diff --git a/.github/workflows/deployment-docker-staging.yml b/.github/workflows/deployment-docker-staging.yml index 73cbd2f8..2607de63 100644 --- a/.github/workflows/deployment-docker-staging.yml +++ b/.github/workflows/deployment-docker-staging.yml @@ -168,6 +168,14 @@ jobs: SERVERPOD_MAX_REQUEST_SIZE: ${{ secrets.SERVERPOD_STAGING_MAX_REQUEST_SIZE }} # The token used to connect with insights must be at least 20 chars SERVERPOD_SERVICE_SECRET: ${{ secrets.SERVERPOD_STAGING_SERVICE_SECRET }} + # Mail configuration for sending emails + SERVERPOD_MAIL_USERNAME: ${{ secrets.SERVERPOD_MAIL_USERNAME }} + SERVERPOD_MAIL_PASSWORD: ${{ secrets.SERVERPOD_MAIL_PASSWORD }} + SERVERPOD_MAIL_SMTP_HOST: ${{ secrets.SERVERPOD_MAIL_SMTP_HOST }} + SERVERPOD_MAIL_ADMIN: ${{ secrets.SERVERPOD_MAIL_ADMIN }} + # Matrix configuration for sending emails + MATRIX_SERVER_URL: ${{ secrets.MATRIX_SERVER_URL }} + MATRIX_AUTH_TOKEN: ${{ secrets.MATRIX_AUTH_TOKEN }} - name: cleanup run: rm -rf ~/.ssh diff --git a/.github/workflows/deployment-docker.yml b/.github/workflows/deployment-docker.yml index a4d9f6e2..d9fa3c15 100644 --- a/.github/workflows/deployment-docker.yml +++ b/.github/workflows/deployment-docker.yml @@ -164,6 +164,14 @@ jobs: SERVERPOD_MAX_REQUEST_SIZE: ${{ secrets.SERVERPOD_MAX_REQUEST_SIZE }} # The token used to connect with insights must be at least 20 chars SERVERPOD_SERVICE_SECRET: ${{ secrets.SERVERPOD_SERVICE_SECRET }} + # Mail configuration for sending emails + SERVERPOD_MAIL_USERNAME: ${{ secrets.SERVERPOD_MAIL_USERNAME }} + SERVERPOD_MAIL_PASSWORD: ${{ secrets.SERVERPOD_MAIL_PASSWORD }} + SERVERPOD_MAIL_SMTP_HOST: ${{ secrets.SERVERPOD_MAIL_SMTP_HOST }} + SERVERPOD_MAIL_ADMIN: ${{ secrets.SERVERPOD_MAIL_ADMIN }} + # Matrix configuration for sending emails + MATRIX_SERVER_URL: ${{ secrets.MATRIX_SERVER_URL }} + MATRIX_AUTH_TOKEN: ${{ secrets.MATRIX_AUTH_TOKEN }} - name: cleanup run: rm -rf ~/.ssh diff --git a/README.md b/README.md index 8d4d72cd..f5440257 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,247 @@ # School Data Hub -Software tool to handle school information flows between teachers / administrative office in an effective, collaborative way. +A comprehensive software tool for managing school information flows between teachers and administrative staff in an effective, collaborative way. -### Origins +## What does it do? -This software originated during the COVID-pandemic in the primary school "Hermannschule" in Stolberg (Germany). In order to keep track on attendance and quarantine statuses a simple app was developed to keep records in a collaborative way with a simple backend built with flask. +School Data Hub integrates with data exported from the NRW Education ministry software ([SVWS](https://www.svws.nrw.de/)) to build extended models of pupils in the backend without uploading any personal data to the server. The backend models are then used to add and manage additional information collaboratively. -Recognizing the time saving potential that brought handling this information digitally, the app was expanded with information sets that are handled every day in schools and cause long ways (walking across the school), cost time and organization resources, or leave long paper trails. +## Technology Stack -### So what does it do? +- **Client**: Flutter (cross-platform mobile and desktop application) +- **Backend**: Serverpod 2.9.1 (Dart-based server framework) +- **Programming Language**: Dart (SDK >=3.8.0) +- **State Management**: watch_it 1.7.0 -The app takes exported information from the software provided by the NRW Education ministry (https://www.svws.nrw.de/) and uses it to build extended models of the schools' pupils in the backend without uploading any personal data to the server^. The models in the backend are then used to add additional information. +## Architecture & Data Protection -### Data protection efforts +### Privacy-First Approach -A singular approach that this app takes is the **transport of personal information through encrypted qr-codes**. It works like this: +School Data Hub implements a unique privacy-first architecture by **decoupling personal information from the database**. Instead of storing personal data on the server, this information is **stored locally on each device and shared through secure data transport between devices**. -1. When the client is installed, it needs school keys. Without them, it is useless. -2. As a security procedure, the school keys are kept securely as an unencrypted qr-code in the school's office. **Whoever wants to use the school data, has to come at least once.** -3. A user comes to the office and scans the school keys containing the URL of the server and encryption keys. These are stored in the device's secure storage. -4. Now the user can log in with their credentials. However, after the login process, there will still not be any pupils' data to call /display. -5. Now, the user has to scan the pupils' credentials, tipically from the desktop version of the app installed in the administrative computer that has the education ministry software installed. With it, a .txt file with the pupils' data can be exported. The desktop version of this app can import this file and create / update data in the backend. The personal data is stored in the secure storage. With it - and with the encryption keys - the app can generate encrypted qr-codes that can be read with other clients to import the pupil identities. API calls are only possible with this pupil data (and jwt tokens for authentication). +### How It Works -#### But why so complicated? +1. **Initial Setup**: When the client is installed, it requires school keys to function. Without these keys, the client cannot access any functionality. -The aim is to ensure that the pupils' personal data isn't sent back and forth through the internet. In fact, this way the personal data is only transported through devices over encrypted qr-codes. As we are storing quite sensitive information, it is crucial that this information can't be related to a concrete pupil without proper authorization. +2. **School Key Distribution**: As a security measure, school keys are stored securely in the school's administrative office. Users must visit the office at least once to obtain access. Users scan school keys containing the server URL and encryption keys. These are stored in the device's secure storage. -**Have you got a better idea? Pull requests welcome! :-)** +3. **Authentication**: After scanning the keys, users can log in with their credentials. However, even after login, no pupil data will be available. -#### What else is encrypted? +4. **Pupil Data Import**: Users must obtain pupil credentials by transferring them from another device (typically from a desktop version of the app). These credentials are stored in secure storage. API calls require both pupil data and JWT tokens for authentication. -For now, all stored images. +### Encrypted Data -### Features already implemented +The following data is encrypted: +- All stored images +- Sensitive information, including special needs support strings -#### Attendance +## Features -- keep track of presence, minutes late, excused / unexcused, gone back home (e.g. for being sick), parents reached/not reached +### Attendance Management -#### Schoolday Events +Track student attendance with comprehensive details: +- Presence/absence status +- Minutes late +- Excused/unexcused absences +- Early departures (e.g., due to illness) +- Parent contact status (reached/not reached) +- Text remarks -- keep track of schoolday events and document them with a picture of a document (or whatever you want to photograph). Schoolday events could be an admoniton, an accident report, a parents meeting (anything you need to document) or any other incident associated with a pupil. - -#### School Lists +### Schoolday Events -- create private (user) or public check lists to keep track of whatever you need to keep track of (paid money for a day trip, signed form from the parents, ...), including a field for comments. - -#### Authorizations +Document and track schoolday events with photo attachments. Events can include: +- Admonitions +- Accident reports +- Parent meetings +- Any other incidents associated with pupils -- authorizations are like lists, but an image can be attached, too. We use it for things that we need to keep a record from, like signed authorizations from the parents/guardians - for instance the authorization to take an avatar picture for this app. - -#### Accounts +### School Lists -- In our school there is a school own currency. It is used as a reward system to motivate pupils. With this currency, they can buy stuff in the school's own shop (like school t-shirts or buttons, but also pencils, erasers, or little games like frisbees and such). +Create private (user-specific) or public checklists to track various tasks and requirements: +- Payment confirmations (e.g., day trips) +- Signed forms from parents +- Any other administrative tasks +- Includes comment fields for additional notes -#### Special information +### Authorizations -- In case parents/guardians provide special information that everyone needs to know (like allergies, emergency medication, likelyhood of having an epilleptic stroke..), it can be called in a special list. +Manage authorizations with document attachments. This feature is used for: +- Signed authorizations from parents/guardians +- Any other authorization documents requiring record-keeping -#### Individual learning support (WIP) +### Accounts & Currency System -Pupils have the right to get learning support according to their individual needs. Being an inclusive school, this also includes special needs. Our local school authority provides mandatory categories that we have to use to describe individual needs and formulate/follow specific educational goals. These have to be documented / kept record of, too. It is also essential, that this information is available to any colleague working with the pupil, and that all colleagues involved cooperate and share information. +Manage a school-specific currency system used as a reward mechanism. Students can earn currency and purchase items from the school shop, including: +- School merchandise (t-shirts, buttons) +- School supplies (pencils, erasers) +- Small games and activities (frisbees, etc.) -- This app can work with any category tree - this means, you could use your own. +### Special Information (WIP) -- Based on this tree, the app can document category statuses - as estimated by the responsible colleague for the pupil - and keep track of progress collaboratively. +Access special information - authorized by parents/guardians - that all staff need to know about students: +- Allergies +- Emergency medication requirements +- Medical conditions (e.g., epilepsy risk) +- Other critical health or safety information -- It also can document development goals, which are ideally formulated together with the pupil. +### Individual Learning Support (WIP) -#### Pupil profile +Document and track individual learning support plans for pupils. This feature supports inclusive education by: -- All information about a pupil is bundled in a pupil profile view. Here you can call additional information like the parents' language proficiency in German (since over 90% of the families in our school do not speak German at home, this is relevant to us), siblings information, or information about afterschool care. +- **Flexible Support Category System**: Works with any category tree structure, allowing schools to use their own categorization system +- **Status Tracking**: Document category statuses as estimated by the responsible teacher +- **Collaborative Progress**: Enable multiple colleagues to track and share progress +- **Development Goals**: Document educational goals, ideally formulated together with the pupil -#### Filters +### Pupil Profile (WIP) -- Filters are incredibly useful when dealing with lists. There are general filters (like class, schoolyear) and specific filters for the different views implemented, as well as different ordering of the list elements according to different criteria depending on the view. +Comprehensive pupil profile view consolidating all information about a student, including: +- Parents' language proficiency in German (important for multilingual families) +- Sibling information with relationship awareness +- Afterschool care details +- All other pupil-related data -#### Matrix: User accounts and room management with Matrix Corporal (WIP) +### Timetable Management (WIP) -- If you configure a synapse server to work with Matrix Corporal https://github.com/devture/matrix-corporal , you can now easily administer accounts and room membership, including per user power levels in the rooms. +Complete timetable management system for scheduling and organizing classes: +- Weekly view with interactive timetable grid +- Multiple lesson groups/classes support +- Subject management with color coding +- Classroom/location management +- Flexible time slot configuration +- Create, edit, and delete scheduled lessons +- Filter by weekday and lesson group -#### Calendar (WIP) +### Library Books Management (WIP) -- Calender view to see / add / delete schooldays. Additionally, the attendance list of the selected date is shown. (To do: add more than one day at a time with https://pub.dev/packages/calendar_date_picker2 ) +Digital library management system for tracking books: +- Book catalog with ISBN support +- Location tracking for library books +- Book tagging system +- Lending management +- Book search functionality +- Multiple book instances per ISBN -#### User management (WIP) +### Workbooks (WIP) -UI to add, update, and delete users. +Manage educational workbooks used by students: +- Track workbook assignments to pupils +- Manage workbook inventory +- Associate workbooks with pupils -### Roadmap +### Competence Management and Report (WIP) -The export of reports in a printable format (pdf) is on top of the list. +Track and manage student competencies: +- Competence tree structure +- Competence checks per pupil +- Competence-based assessments +- Progress tracking +- Semester-based reporting -There are also a couple of models in the backend that are not implemented in the client yet, like a digital library management. +### School Calendar + +Manage school calendar and semesters: +- View schooldays in calendar format +- Add and delete schooldays +- View attendance lists for selected dates +- Semester management + +### User Management + +Administrative interface for managing users: +- Add new users +- Update user information +- Delete users +- Reset passwords +- Manage user roles and permissions + +### Matrix Integration (Matrix Corporal) + +If configured with a Synapse server and [Matrix Corporal](https://github.com/devture/matrix-corporal), manage: +- Matrix user accounts +- Room membership +- Per-user power levels in rooms +- Sending messages from the admin account to users + +## Utilities + +Cross-feature utilities that enhance functionality across the application: + +### PDF Export + +Generate printable PDF reports for: +- Attendance lists (daily and summary reports) +- Individual learning support plans +- School lists +- Missed classes summaries + +### Filters & Search + +Advanced filtering and sorting capabilities: +- General filters (class, school year) +- View-specific filters +- Custom sorting by various criteria +- Quick search functionality + +### Mail Notifications + +Email notification system for various events and updates. ## Setup -You will need a school key - if you don't have one yet, you can generate one with the client. +### Prerequisites -Format for the keys is: +- Flutter SDK (>=3.19.0) +- Dart SDK (>=3.8.0) +- Serverpod server instance -``` +### School Keys + +You will need a school key to configure the client. If you don't have one yet, you can generate one using the client application. + +The school key format is: + +```json { -"server": "{Name of your server}", -"key": "{your encryption key}", -"iv": "{your initialization vector}", -"server_url": "{$your_instance_url/api}" + "server": "Name of your server", + "key": "your encryption key", + "iv": "your initialization vector", + "server_url": "your_instance_url/api" } ``` -### Url for a local development instance - -If you are setting up a local development environment, the `server_url` will depend on which platform you are using for the client: - -- if you are using windows, it will be: `http://127.0.0.1:5000/api` -- if you are using an android simulator, you need to use `http://10.0.2.2:5000/api` - -### TO-DO: +### Local Development Environment -#### Code quality / architecture +When setting up a local development environment, the `server_url` depends on which platform you are using for the client: -- migrate filter architecture to `PupilsFilter` (work in progress) -- subsitute hard coded enum filters for class and school grade with a dynamic solution, so that the app can be used for other schools. **Priority: high** - -#### Functionality +- **Windows**: `http://127.0.0.1:5000/api` +- **Android Emulator**: `http://10.0.2.2:5000/api` -- error handling in API calls. **Priority: high** -- review state management across pages. **Priority: high** -- handle 'no internet connection' case **Priority: high** -- review / implement navigation **Priority: medium** -- internationalization -- generally, improve code quality. The naming of models, functions and such should definitely be revisited by an actual programming person.**Priority: low (it works like this)** +## Roadmap -#### Design +### Planned Features -- review widgets design (rows/columns), and for that matter, the overall design structure (for instance implementing a theme). **Priority: medium** - -#### Features +- Enhanced semester management UI +- Competence reports for school semesters with PDF export +- QR sticker generation as shortcuts for documenting features +- Additional backend models not yet implemented in the client +### Technical Improvements -- UI for semester management -- pdf export feature: individual development plan report. **Priority: high** -- implement competence feature, competence report for a school semester and pdf export. **Priority: high** -- workbook feature **Priority: medium** -- book feature (library lending system) **Priority: medium** -- generate qr stickers as shortcuts for documenting features **Priority: low** +- **Code Quality**: Migrate pupils' filter architecture to `PupilsFilter` (work in progress) +- **Architecture**: Replace hard-coded enum filters for class and school grade with a dynamic solution to support different schools +- **Error Handling**: Improve error handling in API calls +- **State Management**: Review state management across pages +- **Offline Support**: Handle 'no internet connection' scenarios +- **Navigation**: Review and improve navigation patterns +- **Internationalization**: Add multi-language support +- **Design**: Review widget design and implement a comprehensive theme system -### Credits +## Credits -Thanks to the open source community! +Thanks to the open source community for the excellent tools and libraries that make this project possible! -Original code written by @dabblingwithcode . +Original code written by [@dabblingwithcode](https://github.com/dabblingwithcode). -Thanks to @escamoteur (developer of get_it and watch_it) for kindly answering questions and helping out in a couple of sessions with state management, the PupilProxy model and its filters. +Special thanks to [@escamoteur](https://github.com/escamoteur) (developer of `get_it` and `watch_it`) for kindly answering questions and providing guidance on state management, the PupilProxy model, and its filters. diff --git a/school_data_hub_client/lib/src/protocol/_features/user/models/staff_user.dart b/school_data_hub_client/lib/src/protocol/_features/user/models/staff_user.dart index f43d3ad7..481885da 100644 --- a/school_data_hub_client/lib/src/protocol/_features/user/models/staff_user.dart +++ b/school_data_hub_client/lib/src/protocol/_features/user/models/staff_user.dart @@ -24,11 +24,13 @@ abstract class User implements _i1.SerializableModel { required this.userInfoId, this.userInfo, required this.role, + this.matrixUserId, required this.timeUnits, required this.reliefTimeUnits, this.scheduledLessonsTeacher, this.lessonsTeacher, this.pupilsAuth, + this.schooldayEventsProcessingTeam, required this.credit, required this.userFlags, }); @@ -38,11 +40,13 @@ abstract class User implements _i1.SerializableModel { required int userInfoId, _i2.UserInfo? userInfo, required _i3.Role role, + String? matrixUserId, required int timeUnits, required int reliefTimeUnits, List<_i4.ScheduledLessonTeacher>? scheduledLessonsTeacher, List<_i5.LessonTeacher>? lessonsTeacher, Set? pupilsAuth, + String? schooldayEventsProcessingTeam, required int credit, required _i6.UserFlags userFlags, }) = _UserImpl; @@ -56,6 +60,7 @@ abstract class User implements _i1.SerializableModel { : _i2.UserInfo.fromJson( (jsonSerialization['userInfo'] as Map)), role: _i3.Role.fromJson((jsonSerialization['role'] as String)), + matrixUserId: jsonSerialization['matrixUserId'] as String?, timeUnits: jsonSerialization['timeUnits'] as int, reliefTimeUnits: jsonSerialization['reliefTimeUnits'] as int, scheduledLessonsTeacher: (jsonSerialization['scheduledLessonsTeacher'] @@ -71,6 +76,8 @@ abstract class User implements _i1.SerializableModel { : _i1.SetJsonExtension.fromJson( (jsonSerialization['pupilsAuth'] as List), itemFromJson: (e) => e as int), + schooldayEventsProcessingTeam: + jsonSerialization['schooldayEventsProcessingTeam'] as String?, credit: jsonSerialization['credit'] as int, userFlags: _i6.UserFlags.fromJson( (jsonSerialization['userFlags'] as Map)), @@ -88,6 +95,8 @@ abstract class User implements _i1.SerializableModel { _i3.Role role; + String? matrixUserId; + int timeUnits; int reliefTimeUnits; @@ -98,6 +107,8 @@ abstract class User implements _i1.SerializableModel { Set? pupilsAuth; + String? schooldayEventsProcessingTeam; + int credit; _i6.UserFlags userFlags; @@ -110,11 +121,13 @@ abstract class User implements _i1.SerializableModel { int? userInfoId, _i2.UserInfo? userInfo, _i3.Role? role, + String? matrixUserId, int? timeUnits, int? reliefTimeUnits, List<_i4.ScheduledLessonTeacher>? scheduledLessonsTeacher, List<_i5.LessonTeacher>? lessonsTeacher, Set? pupilsAuth, + String? schooldayEventsProcessingTeam, int? credit, _i6.UserFlags? userFlags, }); @@ -125,6 +138,7 @@ abstract class User implements _i1.SerializableModel { 'userInfoId': userInfoId, if (userInfo != null) 'userInfo': userInfo?.toJson(), 'role': role.toJson(), + if (matrixUserId != null) 'matrixUserId': matrixUserId, 'timeUnits': timeUnits, 'reliefTimeUnits': reliefTimeUnits, if (scheduledLessonsTeacher != null) @@ -134,6 +148,8 @@ abstract class User implements _i1.SerializableModel { 'lessonsTeacher': lessonsTeacher?.toJson(valueToJson: (v) => v.toJson()), if (pupilsAuth != null) 'pupilsAuth': pupilsAuth?.toJson(), + if (schooldayEventsProcessingTeam != null) + 'schooldayEventsProcessingTeam': schooldayEventsProcessingTeam, 'credit': credit, 'userFlags': userFlags.toJson(), }; @@ -153,11 +169,13 @@ class _UserImpl extends User { required int userInfoId, _i2.UserInfo? userInfo, required _i3.Role role, + String? matrixUserId, required int timeUnits, required int reliefTimeUnits, List<_i4.ScheduledLessonTeacher>? scheduledLessonsTeacher, List<_i5.LessonTeacher>? lessonsTeacher, Set? pupilsAuth, + String? schooldayEventsProcessingTeam, required int credit, required _i6.UserFlags userFlags, }) : super._( @@ -165,11 +183,13 @@ class _UserImpl extends User { userInfoId: userInfoId, userInfo: userInfo, role: role, + matrixUserId: matrixUserId, timeUnits: timeUnits, reliefTimeUnits: reliefTimeUnits, scheduledLessonsTeacher: scheduledLessonsTeacher, lessonsTeacher: lessonsTeacher, pupilsAuth: pupilsAuth, + schooldayEventsProcessingTeam: schooldayEventsProcessingTeam, credit: credit, userFlags: userFlags, ); @@ -183,11 +203,13 @@ class _UserImpl extends User { int? userInfoId, Object? userInfo = _Undefined, _i3.Role? role, + Object? matrixUserId = _Undefined, int? timeUnits, int? reliefTimeUnits, Object? scheduledLessonsTeacher = _Undefined, Object? lessonsTeacher = _Undefined, Object? pupilsAuth = _Undefined, + Object? schooldayEventsProcessingTeam = _Undefined, int? credit, _i6.UserFlags? userFlags, }) { @@ -197,6 +219,7 @@ class _UserImpl extends User { userInfo: userInfo is _i2.UserInfo? ? userInfo : this.userInfo?.copyWith(), role: role ?? this.role, + matrixUserId: matrixUserId is String? ? matrixUserId : this.matrixUserId, timeUnits: timeUnits ?? this.timeUnits, reliefTimeUnits: reliefTimeUnits ?? this.reliefTimeUnits, scheduledLessonsTeacher: scheduledLessonsTeacher @@ -209,6 +232,9 @@ class _UserImpl extends User { pupilsAuth: pupilsAuth is Set? ? pupilsAuth : this.pupilsAuth?.map((e0) => e0).toSet(), + schooldayEventsProcessingTeam: schooldayEventsProcessingTeam is String? + ? schooldayEventsProcessingTeam + : this.schooldayEventsProcessingTeam, credit: credit ?? this.credit, userFlags: userFlags ?? this.userFlags.copyWith(), ); diff --git a/school_data_hub_client/lib/src/protocol/client.dart b/school_data_hub_client/lib/src/protocol/client.dart index 5ec21515..0413cfc4 100644 --- a/school_data_hub_client/lib/src/protocol/client.dart +++ b/school_data_hub_client/lib/src/protocol/client.dart @@ -75,42 +75,44 @@ import 'package:school_data_hub_client/src/protocol/_features/pupil/models/pupil as _i34; import 'package:school_data_hub_client/src/protocol/_features/learning_support/models/support_level.dart' as _i35; -import 'package:school_data_hub_client/src/protocol/_features/school_data/models/school_data.dart' +import 'package:school_data_hub_client/src/protocol/_features/pupil/models/pupil_data/after_school_care/after_school_care.dart' as _i36; -import 'package:school_data_hub_client/src/protocol/_features/school_lists/models/school_list.dart' +import 'package:school_data_hub_client/src/protocol/_features/school_data/models/school_data.dart' as _i37; -import 'package:school_data_hub_client/src/protocol/_features/school_lists/models/pupil_entry.dart' +import 'package:school_data_hub_client/src/protocol/_features/school_lists/models/school_list.dart' as _i38; -import 'package:school_data_hub_client/src/protocol/_features/schoolday/models/school_semester.dart' +import 'package:school_data_hub_client/src/protocol/_features/school_lists/models/pupil_entry.dart' as _i39; -import 'package:school_data_hub_client/src/protocol/_features/schoolday/models/schoolday.dart' +import 'package:school_data_hub_client/src/protocol/_features/schoolday/models/school_semester.dart' as _i40; -import 'package:school_data_hub_client/src/protocol/_features/schoolday_events/models/schoolday_event.dart' +import 'package:school_data_hub_client/src/protocol/_features/schoolday/models/schoolday.dart' as _i41; -import 'package:school_data_hub_client/src/protocol/_features/schoolday_events/models/schoolday_event_type.dart' +import 'package:school_data_hub_client/src/protocol/_features/schoolday_events/models/schoolday_event.dart' as _i42; -import 'package:school_data_hub_client/src/protocol/_features/timetable/models/classroom.dart' +import 'package:school_data_hub_client/src/protocol/_features/schoolday_events/models/schoolday_event_type.dart' as _i43; -import 'package:school_data_hub_client/src/protocol/_features/timetable/models/lesson/lesson_group.dart' +import 'package:school_data_hub_client/src/protocol/_features/timetable/models/classroom.dart' as _i44; -import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/scheduled_lesson.dart' +import 'package:school_data_hub_client/src/protocol/_features/timetable/models/lesson/lesson_group.dart' as _i45; -import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/lesson_group_membership.dart' +import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/scheduled_lesson.dart' as _i46; -import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/subject.dart' +import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/lesson_group_membership.dart' as _i47; -import 'package:school_data_hub_client/src/protocol/_features/timetable/models/timetable.dart' +import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/subject.dart' as _i48; -import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/timetable_slot.dart' +import 'package:school_data_hub_client/src/protocol/_features/timetable/models/timetable.dart' as _i49; -import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/weekday_enum.dart' +import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/timetable_slot.dart' as _i50; -import 'package:school_data_hub_client/src/protocol/_features/workbooks/models/pupil_workbook.dart' +import 'package:school_data_hub_client/src/protocol/_features/timetable/models/scheduled_lesson/weekday_enum.dart' as _i51; -import 'package:school_data_hub_client/src/protocol/_features/workbooks/models/workbook.dart' +import 'package:school_data_hub_client/src/protocol/_features/workbooks/models/pupil_workbook.dart' as _i52; -import 'dart:typed_data' as _i53; -import 'protocol.dart' as _i54; +import 'package:school_data_hub_client/src/protocol/_features/workbooks/models/workbook.dart' + as _i53; +import 'dart:typed_data' as _i54; +import 'protocol.dart' as _i55; /// The endpoint for admin operations. /// This endpoint requires the user to be logged in and have admin scope. @@ -131,6 +133,9 @@ class EndpointAdmin extends _i1.EndpointRef { required int reliefTimeUnits, required List scopeNames, required bool isTester, + String? schooldayEventsProcessingTeam, + String? matrixUserId, + int? credit, }) => caller.callServerEndpoint<_i3.User>( 'admin', @@ -145,6 +150,9 @@ class EndpointAdmin extends _i1.EndpointRef { 'reliefTimeUnits': reliefTimeUnits, 'scopeNames': scopeNames, 'isTester': isTester, + 'schooldayEventsProcessingTeam': schooldayEventsProcessingTeam, + 'matrixUserId': matrixUserId, + 'credit': credit, }, ); @@ -1416,6 +1424,19 @@ class EndpointPupilUpdate extends _i1.EndpointRef { _i15.mapRecordToJson(schoolyearHeldBackDate), }, ); + + _i2.Future<_i5.PupilData> updateAfterSchoolCare( + int pupilId, + _i36.AfterSchoolCare afterSchoolCare, + ) => + caller.callServerEndpoint<_i5.PupilData>( + 'pupilUpdate', + 'updateAfterSchoolCare', + { + 'pupilId': pupilId, + 'afterSchoolCare': afterSchoolCare, + }, + ); } /// {@category Endpoint} @@ -1425,16 +1446,16 @@ class EndpointSchoolData extends _i1.EndpointRef { @override String get name => 'schoolData'; - _i2.Future<_i36.SchoolData> postSchoolData(_i36.SchoolData schoolData) => - caller.callServerEndpoint<_i36.SchoolData>( + _i2.Future<_i37.SchoolData> postSchoolData(_i37.SchoolData schoolData) => + caller.callServerEndpoint<_i37.SchoolData>( 'schoolData', 'postSchoolData', {'schoolData': schoolData}, ); /// TODO: we should be specific about which school data to get - _i2.Future<_i36.SchoolData?> getSchoolData() => - caller.callServerEndpoint<_i36.SchoolData?>( + _i2.Future<_i37.SchoolData?> getSchoolData() => + caller.callServerEndpoint<_i37.SchoolData?>( 'schoolData', 'getSchoolData', {}, @@ -1448,21 +1469,21 @@ class EndpointSchoolList extends _i1.EndpointRef { @override String get name => 'schoolList'; - _i2.Future> fetchSchoolLists(String userName) => - caller.callServerEndpoint>( + _i2.Future> fetchSchoolLists(String userName) => + caller.callServerEndpoint>( 'schoolList', 'fetchSchoolLists', {'userName': userName}, ); - _i2.Future<_i37.SchoolList> postSchoolList( + _i2.Future<_i38.SchoolList> postSchoolList( String name, String description, List pupilIds, bool public, String createdBy, ) => - caller.callServerEndpoint<_i37.SchoolList>( + caller.callServerEndpoint<_i38.SchoolList>( 'schoolList', 'postSchoolList', { @@ -1474,7 +1495,7 @@ class EndpointSchoolList extends _i1.EndpointRef { }, ); - _i2.Future<_i37.SchoolList> updateSchoolList( + _i2.Future<_i38.SchoolList> updateSchoolList( int listId, String? name, String? description, @@ -1482,7 +1503,7 @@ class EndpointSchoolList extends _i1.EndpointRef { bool? public, ({_i14.MemberOperation operation, List pupilIds})? updateMembers, ) => - caller.callServerEndpoint<_i37.SchoolList>( + caller.callServerEndpoint<_i38.SchoolList>( 'schoolList', 'updateSchoolList', { @@ -1502,9 +1523,9 @@ class EndpointSchoolList extends _i1.EndpointRef { {'listId': listId}, ); - _i2.Future<_i38.PupilListEntry> updatePupilListEntry( - _i38.PupilListEntry entry) => - caller.callServerEndpoint<_i38.PupilListEntry>( + _i2.Future<_i39.PupilListEntry> updatePupilListEntry( + _i39.PupilListEntry entry) => + caller.callServerEndpoint<_i39.PupilListEntry>( 'schoolList', 'updatePupilListEntry', {'entry': entry}, @@ -1518,7 +1539,7 @@ class EndpointSchooldayAdmin extends _i1.EndpointRef { @override String get name => 'schooldayAdmin'; - _i2.Future<_i39.SchoolSemester> createSchoolSemester( + _i2.Future<_i40.SchoolSemester> createSchoolSemester( String schoolYearName, DateTime startDate, DateTime endDate, @@ -1528,7 +1549,7 @@ class EndpointSchooldayAdmin extends _i1.EndpointRef { DateTime? reportConferenceDate, DateTime? reportSignedDate, ) => - caller.callServerEndpoint<_i39.SchoolSemester>( + caller.callServerEndpoint<_i40.SchoolSemester>( 'schooldayAdmin', 'createSchoolSemester', { @@ -1543,43 +1564,43 @@ class EndpointSchooldayAdmin extends _i1.EndpointRef { }, ); - _i2.Future> getAllSchoolSemesters() => - caller.callServerEndpoint>( + _i2.Future> getAllSchoolSemesters() => + caller.callServerEndpoint>( 'schooldayAdmin', 'getAllSchoolSemesters', {}, ); - _i2.Future<_i39.SchoolSemester?> getCurrentSchoolSemester() => - caller.callServerEndpoint<_i39.SchoolSemester?>( + _i2.Future<_i40.SchoolSemester?> getCurrentSchoolSemester() => + caller.callServerEndpoint<_i40.SchoolSemester?>( 'schooldayAdmin', 'getCurrentSchoolSemester', {}, ); - _i2.Future updateSchoolSemester(_i39.SchoolSemester schoolSemester) => + _i2.Future updateSchoolSemester(_i40.SchoolSemester schoolSemester) => caller.callServerEndpoint( 'schooldayAdmin', 'updateSchoolSemester', {'schoolSemester': schoolSemester}, ); - _i2.Future deleteSchoolSemester(_i39.SchoolSemester semester) => + _i2.Future deleteSchoolSemester(_i40.SchoolSemester semester) => caller.callServerEndpoint( 'schooldayAdmin', 'deleteSchoolSemester', {'semester': semester}, ); - _i2.Future<_i40.Schoolday?> createSchoolday(DateTime date) => - caller.callServerEndpoint<_i40.Schoolday?>( + _i2.Future<_i41.Schoolday?> createSchoolday(DateTime date) => + caller.callServerEndpoint<_i41.Schoolday?>( 'schooldayAdmin', 'createSchoolday', {'date': date}, ); - _i2.Future> createSchooldays(List dates) => - caller.callServerEndpoint>( + _i2.Future> createSchooldays(List dates) => + caller.callServerEndpoint>( 'schooldayAdmin', 'createSchooldays', {'dates': dates}, @@ -1592,7 +1613,7 @@ class EndpointSchooldayAdmin extends _i1.EndpointRef { {'date': date}, ); - _i2.Future updateSchoolday(_i40.Schoolday schoolday) => + _i2.Future updateSchoolday(_i41.Schoolday schoolday) => caller.callServerEndpoint( 'schooldayAdmin', 'updateSchoolday', @@ -1607,15 +1628,15 @@ class EndpointSchoolday extends _i1.EndpointRef { @override String get name => 'schoolday'; - _i2.Future> getSchoolSemesters() => - caller.callServerEndpoint>( + _i2.Future> getSchoolSemesters() => + caller.callServerEndpoint>( 'schoolday', 'getSchoolSemesters', {}, ); - _i2.Future> getSchooldays() => - caller.callServerEndpoint>( + _i2.Future> getSchooldays() => + caller.callServerEndpoint>( 'schoolday', 'getSchooldays', {}, @@ -1629,42 +1650,56 @@ class EndpointSchooldayEvent extends _i1.EndpointRef { @override String get name => 'schooldayEvent'; - _i2.Future> fetchSchooldayEvents() => - caller.callServerEndpoint>( + _i2.Future> fetchSchooldayEvents() => + caller.callServerEndpoint>( 'schooldayEvent', 'fetchSchooldayEvents', {}, ); - _i2.Future<_i41.SchooldayEvent> createSchooldayEvent({ + _i2.Future<_i42.SchooldayEvent> createSchooldayEvent({ required int pupilId, + required String pupilNameAndGroup, + required String dateTimeAsString, required int schooldayId, - required _i42.SchooldayEventType type, + required _i43.SchooldayEventType type, required String reason, required String createdBy, + required String tutor, }) => - caller.callServerEndpoint<_i41.SchooldayEvent>( + caller.callServerEndpoint<_i42.SchooldayEvent>( 'schooldayEvent', 'createSchooldayEvent', { 'pupilId': pupilId, + 'pupilNameAndGroup': pupilNameAndGroup, + 'dateTimeAsString': dateTimeAsString, 'schooldayId': schooldayId, 'type': type, 'reason': reason, 'createdBy': createdBy, + 'tutor': tutor, }, ); - _i2.Future<_i41.SchooldayEvent> updateSchooldayEvent( - _i41.SchooldayEvent schooldayEvent, - bool changedProcessedToFalse, + _i2.Future<_i42.SchooldayEvent> updateSchooldayEvent( + _i42.SchooldayEvent schooldayEvent, + bool changedProcessedStatus, + String pupilNameAndGroup, + String tutor, + String modifiedBy, + String dateTimeAsString, ) => - caller.callServerEndpoint<_i41.SchooldayEvent>( + caller.callServerEndpoint<_i42.SchooldayEvent>( 'schooldayEvent', 'updateSchooldayEvent', { 'schooldayEvent': schooldayEvent, - 'changedProcessedToFalse': changedProcessedToFalse, + 'changedProcessedStatus': changedProcessedStatus, + 'pupilNameAndGroup': pupilNameAndGroup, + 'tutor': tutor, + 'modifiedBy': modifiedBy, + 'dateTimeAsString': dateTimeAsString, }, ); @@ -1675,13 +1710,13 @@ class EndpointSchooldayEvent extends _i1.EndpointRef { {'schooldayEventId': schooldayEventId}, ); - _i2.Future<_i41.SchooldayEvent> updateSchooldayEventFile( + _i2.Future<_i42.SchooldayEvent> updateSchooldayEventFile( int schooldayEventId, String filePath, String createdBy, bool isprocessed, ) => - caller.callServerEndpoint<_i41.SchooldayEvent>( + caller.callServerEndpoint<_i42.SchooldayEvent>( 'schooldayEvent', 'updateSchooldayEventFile', { @@ -1692,11 +1727,11 @@ class EndpointSchooldayEvent extends _i1.EndpointRef { }, ); - _i2.Future<_i41.SchooldayEvent> deleteSchooldayEventFile( + _i2.Future<_i42.SchooldayEvent> deleteSchooldayEventFile( int schooldayEventId, bool isProcessed, ) => - caller.callServerEndpoint<_i41.SchooldayEvent>( + caller.callServerEndpoint<_i42.SchooldayEvent>( 'schooldayEvent', 'deleteSchooldayEventFile', { @@ -1713,43 +1748,43 @@ class EndpointClassroom extends _i1.EndpointRef { @override String get name => 'classroom'; - _i2.Future<_i43.Classroom> createClassroom(_i43.Classroom classroom) => - caller.callServerEndpoint<_i43.Classroom>( + _i2.Future<_i44.Classroom> createClassroom(_i44.Classroom classroom) => + caller.callServerEndpoint<_i44.Classroom>( 'classroom', 'createClassroom', {'classroom': classroom}, ); - _i2.Future> fetchClassrooms() => - caller.callServerEndpoint>( + _i2.Future> fetchClassrooms() => + caller.callServerEndpoint>( 'classroom', 'fetchClassrooms', {}, ); - _i2.Future<_i43.Classroom?> fetchClassroomById(int id) => - caller.callServerEndpoint<_i43.Classroom?>( + _i2.Future<_i44.Classroom?> fetchClassroomById(int id) => + caller.callServerEndpoint<_i44.Classroom?>( 'classroom', 'fetchClassroomById', {'id': id}, ); - _i2.Future<_i43.Classroom?> fetchClassroomByRoomCode(String roomCode) => - caller.callServerEndpoint<_i43.Classroom?>( + _i2.Future<_i44.Classroom?> fetchClassroomByRoomCode(String roomCode) => + caller.callServerEndpoint<_i44.Classroom?>( 'classroom', 'fetchClassroomByRoomCode', {'roomCode': roomCode}, ); - _i2.Future> fetchClassroomsByRoomName(String roomName) => - caller.callServerEndpoint>( + _i2.Future> fetchClassroomsByRoomName(String roomName) => + caller.callServerEndpoint>( 'classroom', 'fetchClassroomsByRoomName', {'roomName': roomName}, ); - _i2.Future<_i43.Classroom> updateClassroom(_i43.Classroom classroom) => - caller.callServerEndpoint<_i43.Classroom>( + _i2.Future<_i44.Classroom> updateClassroom(_i44.Classroom classroom) => + caller.callServerEndpoint<_i44.Classroom>( 'classroom', 'updateClassroom', {'classroom': classroom}, @@ -1769,61 +1804,61 @@ class EndpointLearningGroup extends _i1.EndpointRef { @override String get name => 'learningGroup'; - _i2.Future<_i44.LessonGroup> createLessonGroup( - _i44.LessonGroup lessonGroup) => - caller.callServerEndpoint<_i44.LessonGroup>( + _i2.Future<_i45.LessonGroup> createLessonGroup( + _i45.LessonGroup lessonGroup) => + caller.callServerEndpoint<_i45.LessonGroup>( 'learningGroup', 'createLessonGroup', {'lessonGroup': lessonGroup}, ); - _i2.Future> fetchLessonGroups() => - caller.callServerEndpoint>( + _i2.Future> fetchLessonGroups() => + caller.callServerEndpoint>( 'learningGroup', 'fetchLessonGroups', {}, ); - _i2.Future<_i44.LessonGroup?> fetchLessonGroupById(int id) => - caller.callServerEndpoint<_i44.LessonGroup?>( + _i2.Future<_i45.LessonGroup?> fetchLessonGroupById(int id) => + caller.callServerEndpoint<_i45.LessonGroup?>( 'learningGroup', 'fetchLessonGroupById', {'id': id}, ); - _i2.Future<_i44.LessonGroup?> fetchLessonGroupByPublicId(String publicId) => - caller.callServerEndpoint<_i44.LessonGroup?>( + _i2.Future<_i45.LessonGroup?> fetchLessonGroupByPublicId(String publicId) => + caller.callServerEndpoint<_i45.LessonGroup?>( 'learningGroup', 'fetchLessonGroupByPublicId', {'publicId': publicId}, ); - _i2.Future> fetchLessonGroupsByName(String name) => - caller.callServerEndpoint>( + _i2.Future> fetchLessonGroupsByName(String name) => + caller.callServerEndpoint>( 'learningGroup', 'fetchLessonGroupsByName', {'name': name}, ); - _i2.Future> fetchLessonGroupsByCreator( + _i2.Future> fetchLessonGroupsByCreator( String createdBy) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'learningGroup', 'fetchLessonGroupsByCreator', {'createdBy': createdBy}, ); - _i2.Future> fetchLessonGroupsByTimetable( + _i2.Future> fetchLessonGroupsByTimetable( int timetableId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'learningGroup', 'fetchLessonGroupsByTimetable', {'timetableId': timetableId}, ); - _i2.Future<_i44.LessonGroup> updateLessonGroup( - _i44.LessonGroup lessonGroup) => - caller.callServerEndpoint<_i44.LessonGroup>( + _i2.Future<_i45.LessonGroup> updateLessonGroup( + _i45.LessonGroup lessonGroup) => + caller.callServerEndpoint<_i45.LessonGroup>( 'learningGroup', 'updateLessonGroup', {'lessonGroup': lessonGroup}, @@ -1843,77 +1878,77 @@ class EndpointScheduledLesson extends _i1.EndpointRef { @override String get name => 'scheduledLesson'; - _i2.Future<_i45.ScheduledLesson?> createScheduledLesson( - _i45.ScheduledLesson scheduledLesson) => - caller.callServerEndpoint<_i45.ScheduledLesson?>( + _i2.Future<_i46.ScheduledLesson?> createScheduledLesson( + _i46.ScheduledLesson scheduledLesson) => + caller.callServerEndpoint<_i46.ScheduledLesson?>( 'scheduledLesson', 'createScheduledLesson', {'scheduledLesson': scheduledLesson}, ); - _i2.Future> fetchScheduledLessons() => - caller.callServerEndpoint>( + _i2.Future> fetchScheduledLessons() => + caller.callServerEndpoint>( 'scheduledLesson', 'fetchScheduledLessons', {}, ); - _i2.Future<_i45.ScheduledLesson?> fetchScheduledLessonById(int id) => - caller.callServerEndpoint<_i45.ScheduledLesson?>( + _i2.Future<_i46.ScheduledLesson?> fetchScheduledLessonById(int id) => + caller.callServerEndpoint<_i46.ScheduledLesson?>( 'scheduledLesson', 'fetchScheduledLessonById', {'id': id}, ); - _i2.Future> fetchScheduledLessonsByTimetable( + _i2.Future> fetchScheduledLessonsByTimetable( int timetableId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'scheduledLesson', 'fetchScheduledLessonsByTimetable', {'timetableId': timetableId}, ); - _i2.Future> fetchScheduledLessonsBySubject( + _i2.Future> fetchScheduledLessonsBySubject( int subjectId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'scheduledLesson', 'fetchScheduledLessonsBySubject', {'subjectId': subjectId}, ); - _i2.Future> fetchScheduledLessonsByRoom( + _i2.Future> fetchScheduledLessonsByRoom( int roomId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'scheduledLesson', 'fetchScheduledLessonsByRoom', {'roomId': roomId}, ); - _i2.Future> fetchScheduledLessonsBySlotId( + _i2.Future> fetchScheduledLessonsBySlotId( int slotId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'scheduledLesson', 'fetchScheduledLessonsBySlotId', {'slotId': slotId}, ); - _i2.Future> fetchActiveScheduledLessons() => - caller.callServerEndpoint>( + _i2.Future> fetchActiveScheduledLessons() => + caller.callServerEndpoint>( 'scheduledLesson', 'fetchActiveScheduledLessons', {}, ); - _i2.Future<_i45.ScheduledLesson?> updateScheduledLesson( - _i45.ScheduledLesson scheduledLesson) => - caller.callServerEndpoint<_i45.ScheduledLesson?>( + _i2.Future<_i46.ScheduledLesson?> updateScheduledLesson( + _i46.ScheduledLesson scheduledLesson) => + caller.callServerEndpoint<_i46.ScheduledLesson?>( 'scheduledLesson', 'updateScheduledLesson', {'scheduledLesson': scheduledLesson}, ); - _i2.Future<_i45.ScheduledLesson?> deactivateScheduledLesson(int id) => - caller.callServerEndpoint<_i45.ScheduledLesson?>( + _i2.Future<_i46.ScheduledLesson?> deactivateScheduledLesson(int id) => + caller.callServerEndpoint<_i46.ScheduledLesson?>( 'scheduledLesson', 'deactivateScheduledLesson', {'id': id}, @@ -1935,53 +1970,53 @@ class EndpointScheduledLessonGroupMembership extends _i1.EndpointRef { @override String get name => 'scheduledLessonGroupMembership'; - _i2.Future<_i46.ScheduledLessonGroupMembership> + _i2.Future<_i47.ScheduledLessonGroupMembership> createScheduledLessonGroupMembership( - _i46.ScheduledLessonGroupMembership membership) => - caller.callServerEndpoint<_i46.ScheduledLessonGroupMembership>( + _i47.ScheduledLessonGroupMembership membership) => + caller.callServerEndpoint<_i47.ScheduledLessonGroupMembership>( 'scheduledLessonGroupMembership', 'createScheduledLessonGroupMembership', {'membership': membership}, ); - _i2.Future> + _i2.Future> fetchScheduledLessonGroupMemberships() => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'scheduledLessonGroupMembership', 'fetchScheduledLessonGroupMemberships', {}, ); - _i2.Future<_i46.ScheduledLessonGroupMembership?> + _i2.Future<_i47.ScheduledLessonGroupMembership?> fetchScheduledLessonGroupMembershipById(int id) => - caller.callServerEndpoint<_i46.ScheduledLessonGroupMembership?>( + caller.callServerEndpoint<_i47.ScheduledLessonGroupMembership?>( 'scheduledLessonGroupMembership', 'fetchScheduledLessonGroupMembershipById', {'id': id}, ); - _i2.Future> + _i2.Future> fetchMembershipsByLessonGroupId(int lessonGroupId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'scheduledLessonGroupMembership', 'fetchMembershipsByLessonGroupId', {'lessonGroupId': lessonGroupId}, ); - _i2.Future> + _i2.Future> fetchMembershipsByPupilDataId(int pupilDataId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'scheduledLessonGroupMembership', 'fetchMembershipsByPupilDataId', {'pupilDataId': pupilDataId}, ); - _i2.Future<_i46.ScheduledLessonGroupMembership?> + _i2.Future<_i47.ScheduledLessonGroupMembership?> fetchMembershipByLessonGroupAndPupil( int lessonGroupId, int pupilDataId, ) => - caller.callServerEndpoint<_i46.ScheduledLessonGroupMembership?>( + caller.callServerEndpoint<_i47.ScheduledLessonGroupMembership?>( 'scheduledLessonGroupMembership', 'fetchMembershipByLessonGroupAndPupil', { @@ -1990,10 +2025,10 @@ class EndpointScheduledLessonGroupMembership extends _i1.EndpointRef { }, ); - _i2.Future<_i46.ScheduledLessonGroupMembership> + _i2.Future<_i47.ScheduledLessonGroupMembership> updateScheduledLessonGroupMembership( - _i46.ScheduledLessonGroupMembership membership) => - caller.callServerEndpoint<_i46.ScheduledLessonGroupMembership>( + _i47.ScheduledLessonGroupMembership membership) => + caller.callServerEndpoint<_i47.ScheduledLessonGroupMembership>( 'scheduledLessonGroupMembership', 'updateScheduledLessonGroupMembership', {'membership': membership}, @@ -2040,50 +2075,50 @@ class EndpointSubject extends _i1.EndpointRef { @override String get name => 'subject'; - _i2.Future<_i47.Subject> createSubject(_i47.Subject subject) => - caller.callServerEndpoint<_i47.Subject>( + _i2.Future<_i48.Subject> createSubject(_i48.Subject subject) => + caller.callServerEndpoint<_i48.Subject>( 'subject', 'createSubject', {'subject': subject}, ); - _i2.Future> fetchSubjects() => - caller.callServerEndpoint>( + _i2.Future> fetchSubjects() => + caller.callServerEndpoint>( 'subject', 'fetchSubjects', {}, ); - _i2.Future<_i47.Subject?> fetchSubjectById(int id) => - caller.callServerEndpoint<_i47.Subject?>( + _i2.Future<_i48.Subject?> fetchSubjectById(int id) => + caller.callServerEndpoint<_i48.Subject?>( 'subject', 'fetchSubjectById', {'id': id}, ); - _i2.Future<_i47.Subject?> fetchSubjectByPublicId(String publicId) => - caller.callServerEndpoint<_i47.Subject?>( + _i2.Future<_i48.Subject?> fetchSubjectByPublicId(String publicId) => + caller.callServerEndpoint<_i48.Subject?>( 'subject', 'fetchSubjectByPublicId', {'publicId': publicId}, ); - _i2.Future> fetchSubjectsByName(String name) => - caller.callServerEndpoint>( + _i2.Future> fetchSubjectsByName(String name) => + caller.callServerEndpoint>( 'subject', 'fetchSubjectsByName', {'name': name}, ); - _i2.Future> fetchSubjectsByCreator(String createdBy) => - caller.callServerEndpoint>( + _i2.Future> fetchSubjectsByCreator(String createdBy) => + caller.callServerEndpoint>( 'subject', 'fetchSubjectsByCreator', {'createdBy': createdBy}, ); - _i2.Future<_i47.Subject> updateSubject(_i47.Subject subject) => - caller.callServerEndpoint<_i47.Subject>( + _i2.Future<_i48.Subject> updateSubject(_i48.Subject subject) => + caller.callServerEndpoint<_i48.Subject>( 'subject', 'updateSubject', {'subject': subject}, @@ -2103,65 +2138,65 @@ class EndpointTimetable extends _i1.EndpointRef { @override String get name => 'timetable'; - _i2.Future<_i48.Timetable> createTimetable(_i48.Timetable timetable) => - caller.callServerEndpoint<_i48.Timetable>( + _i2.Future<_i49.Timetable> createTimetable(_i49.Timetable timetable) => + caller.callServerEndpoint<_i49.Timetable>( 'timetable', 'createTimetable', {'timetable': timetable}, ); - _i2.Future> fetchTimetables() => - caller.callServerEndpoint>( + _i2.Future> fetchTimetables() => + caller.callServerEndpoint>( 'timetable', 'fetchTimetables', {}, ); - _i2.Future<_i48.Timetable?> fetchTimetableById(int id) => - caller.callServerEndpoint<_i48.Timetable?>( + _i2.Future<_i49.Timetable?> fetchTimetableById(int id) => + caller.callServerEndpoint<_i49.Timetable?>( 'timetable', 'fetchTimetableById', {'id': id}, ); - _i2.Future<_i48.Timetable?> fetchTimetable() => - caller.callServerEndpoint<_i48.Timetable?>( + _i2.Future<_i49.Timetable?> fetchTimetable() => + caller.callServerEndpoint<_i49.Timetable?>( 'timetable', 'fetchTimetable', {}, ); - _i2.Future<_i48.Timetable?> fetchCompleteTimetableData() => - caller.callServerEndpoint<_i48.Timetable?>( + _i2.Future<_i49.Timetable?> fetchCompleteTimetableData() => + caller.callServerEndpoint<_i49.Timetable?>( 'timetable', 'fetchCompleteTimetableData', {}, ); - _i2.Future> fetchActiveTimetables() => - caller.callServerEndpoint>( + _i2.Future> fetchActiveTimetables() => + caller.callServerEndpoint>( 'timetable', 'fetchActiveTimetables', {}, ); - _i2.Future> fetchTimetablesBySemester( + _i2.Future> fetchTimetablesBySemester( int schoolSemesterId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'timetable', 'fetchTimetablesBySemester', {'schoolSemesterId': schoolSemesterId}, ); - _i2.Future<_i48.Timetable> updateTimetable(_i48.Timetable timetable) => - caller.callServerEndpoint<_i48.Timetable>( + _i2.Future<_i49.Timetable> updateTimetable(_i49.Timetable timetable) => + caller.callServerEndpoint<_i49.Timetable>( 'timetable', 'updateTimetable', {'timetable': timetable}, ); - _i2.Future<_i48.Timetable> deactivateTimetable(int id) => - caller.callServerEndpoint<_i48.Timetable>( + _i2.Future<_i49.Timetable> deactivateTimetable(int id) => + caller.callServerEndpoint<_i49.Timetable>( 'timetable', 'deactivateTimetable', {'id': id}, @@ -2181,47 +2216,47 @@ class EndpointTimetableSlot extends _i1.EndpointRef { @override String get name => 'timetableSlot'; - _i2.Future<_i49.TimetableSlot> createTimetableSlot( - _i49.TimetableSlot timetableSlot) => - caller.callServerEndpoint<_i49.TimetableSlot>( + _i2.Future<_i50.TimetableSlot> createTimetableSlot( + _i50.TimetableSlot timetableSlot) => + caller.callServerEndpoint<_i50.TimetableSlot>( 'timetableSlot', 'createTimetableSlot', {'timetableSlot': timetableSlot}, ); - _i2.Future> fetchTimetableSlots() => - caller.callServerEndpoint>( + _i2.Future> fetchTimetableSlots() => + caller.callServerEndpoint>( 'timetableSlot', 'fetchTimetableSlots', {}, ); - _i2.Future<_i49.TimetableSlot?> fetchTimetableSlotById(int id) => - caller.callServerEndpoint<_i49.TimetableSlot?>( + _i2.Future<_i50.TimetableSlot?> fetchTimetableSlotById(int id) => + caller.callServerEndpoint<_i50.TimetableSlot?>( 'timetableSlot', 'fetchTimetableSlotById', {'id': id}, ); - _i2.Future> fetchTimetableSlotsByTimetableId( + _i2.Future> fetchTimetableSlotsByTimetableId( int timetableId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'timetableSlot', 'fetchTimetableSlotsByTimetableId', {'timetableId': timetableId}, ); - _i2.Future> fetchTimetableSlotsByDay( - _i50.Weekday day) => - caller.callServerEndpoint>( + _i2.Future> fetchTimetableSlotsByDay( + _i51.Weekday day) => + caller.callServerEndpoint>( 'timetableSlot', 'fetchTimetableSlotsByDay', {'day': day}, ); - _i2.Future<_i49.TimetableSlot> updateTimetableSlot( - _i49.TimetableSlot timetableSlot) => - caller.callServerEndpoint<_i49.TimetableSlot>( + _i2.Future<_i50.TimetableSlot> updateTimetableSlot( + _i50.TimetableSlot timetableSlot) => + caller.callServerEndpoint<_i50.TimetableSlot>( 'timetableSlot', 'updateTimetableSlot', {'timetableSlot': timetableSlot}, @@ -2283,12 +2318,12 @@ class EndpointPupilWorkbooks extends _i1.EndpointRef { @override String get name => 'pupilWorkbooks'; - _i2.Future<_i51.PupilWorkbook> postPupilWorkbook( + _i2.Future<_i52.PupilWorkbook> postPupilWorkbook( int isbn, int pupilId, String createdBy, ) => - caller.callServerEndpoint<_i51.PupilWorkbook>( + caller.callServerEndpoint<_i52.PupilWorkbook>( 'pupilWorkbooks', 'postPupilWorkbook', { @@ -2298,24 +2333,24 @@ class EndpointPupilWorkbooks extends _i1.EndpointRef { }, ); - _i2.Future> fetchPupilWorkbooks() => - caller.callServerEndpoint>( + _i2.Future> fetchPupilWorkbooks() => + caller.callServerEndpoint>( 'pupilWorkbooks', 'fetchPupilWorkbooks', {}, ); - _i2.Future> fetchPupilWorkbooksFromPupil( + _i2.Future> fetchPupilWorkbooksFromPupil( int pupilId) => - caller.callServerEndpoint>( + caller.callServerEndpoint>( 'pupilWorkbooks', 'fetchPupilWorkbooksFromPupil', {'pupilId': pupilId}, ); - _i2.Future<_i51.PupilWorkbook> updatePupilWorkbook( - _i51.PupilWorkbook pupilWorkbook) => - caller.callServerEndpoint<_i51.PupilWorkbook>( + _i2.Future<_i52.PupilWorkbook> updatePupilWorkbook( + _i52.PupilWorkbook pupilWorkbook) => + caller.callServerEndpoint<_i52.PupilWorkbook>( 'pupilWorkbooks', 'updatePupilWorkbook', {'pupilWorkbook': pupilWorkbook}, @@ -2336,29 +2371,29 @@ class EndpointWorkbooks extends _i1.EndpointRef { @override String get name => 'workbooks'; - _i2.Future<_i52.Workbook> postWorkbook(_i52.Workbook workbook) => - caller.callServerEndpoint<_i52.Workbook>( + _i2.Future<_i53.Workbook> postWorkbook(_i53.Workbook workbook) => + caller.callServerEndpoint<_i53.Workbook>( 'workbooks', 'postWorkbook', {'workbook': workbook}, ); - _i2.Future<_i52.Workbook> fetchWorkbookByIsbn(int isbn) => - caller.callServerEndpoint<_i52.Workbook>( + _i2.Future<_i53.Workbook> fetchWorkbookByIsbn(int isbn) => + caller.callServerEndpoint<_i53.Workbook>( 'workbooks', 'fetchWorkbookByIsbn', {'isbn': isbn}, ); - _i2.Future> fetchWorkbooks() => - caller.callServerEndpoint>( + _i2.Future> fetchWorkbooks() => + caller.callServerEndpoint>( 'workbooks', 'fetchWorkbooks', {}, ); - _i2.Future<_i52.Workbook> updateWorkbook(_i52.Workbook workbook) => - caller.callServerEndpoint<_i52.Workbook>( + _i2.Future<_i53.Workbook> updateWorkbook(_i53.Workbook workbook) => + caller.callServerEndpoint<_i53.Workbook>( 'workbooks', 'updateWorkbook', {'workbook': workbook}, @@ -2409,15 +2444,15 @@ class EndpointFiles extends _i1.EndpointRef { ); /// As described in https://docs.serverpod.dev/concepts/file-uploads#client-side-code - _i2.Future<_i53.ByteData?> getImage(String documentId) => - caller.callServerEndpoint<_i53.ByteData?>( + _i2.Future<_i54.ByteData?> getImage(String documentId) => + caller.callServerEndpoint<_i54.ByteData?>( 'files', 'getImage', {'documentId': documentId}, ); - _i2.Future<_i53.ByteData?> getUnencryptedImage(String path) => - caller.callServerEndpoint<_i53.ByteData?>( + _i2.Future<_i54.ByteData?> getUnencryptedImage(String path) => + caller.callServerEndpoint<_i54.ByteData?>( 'files', 'getUnencryptedImage', {'path': path}, @@ -2448,7 +2483,7 @@ class Client extends _i1.ServerpodClientShared { bool? disconnectStreamsOnLostInternetConnection, }) : super( host, - _i54.Protocol(), + _i55.Protocol(), securityContext: securityContext, authenticationKeyManager: authenticationKeyManager, streamingConnectionTimeout: streamingConnectionTimeout, diff --git a/school_data_hub_flutter/.gitignore b/school_data_hub_flutter/.gitignore index c7db5674..b69e5706 100644 --- a/school_data_hub_flutter/.gitignore +++ b/school_data_hub_flutter/.gitignore @@ -23,7 +23,11 @@ # debug related files matrix-policy.json matrix-fetched-policy.json +updated-policy.json +rooms.json test_enc.dart +decrypt_files.dart +**/scripts/ # Flutter/Dart/Pub related **/doc/api/ diff --git a/school_data_hub_flutter/README.md b/school_data_hub_flutter/README.md index ff51c892..0475c7c8 100644 --- a/school_data_hub_flutter/README.md +++ b/school_data_hub_flutter/README.md @@ -1,13 +1,373 @@ -# school_data_hub_flutter +# School Data Hub Flutter Client -- Deploying / migrations in production -- Storage, backup -- Implement Mail -- Migrate Auth / check device related Auth / revoke device Auth -- Manager reinitialization changing instance / logging out -- Architecture / data flow -- UI / layout -- Implement https://github.com/mduccc/in_app_console ? +The Flutter cross-platform client application for School Data Hub. This app provides the user interface for managing school information flows, working in conjunction with the School Data Hub Server. + +## Overview + +The Flutter client is a cross-platform application supporting: +- **Desktop**: Windows, Linux, macOS +- **Mobile**: Android, iOS +- **Web**: (Limited support) + +It implements a privacy-first architecture where sensitive pupil data is stored locally on each device and never uploaded to the server. + +**Shorebird Integration**: The client uses Shorebird for over-the-air (OTA) code push updates, allowing for rapid deployment of bug fixes and feature updates without requiring app store releases. + +## Prerequisites + +- **Flutter SDK**: >=3.19.0 +- **Dart SDK**: >=3.8.0 +- **Serverpod**: The project uses Serverpod 2.9.1 for backend communication +- **School Data Hub Client**: A local dependency (`../school_data_hub_client`) ## Getting Started +### Installation + +1. Clone the repository and navigate to the Flutter client directory: + ```bash + cd school_data_hub_flutter + ``` + +2. Install dependencies: + ```bash + flutter pub get + ``` + +3. Ensure the `school_data_hub_client` package is generated. You may need to run `serverpod generate` in the parent directory first. + +### Running the App + +#### Development Mode + +Run the app in development mode: +```bash +flutter run +``` + +For a specific platform: +```bash +# Windows +flutter run -d windows + +# Android +flutter run -d android + +# iOS (macOS only) +flutter run -d ios + +# Linux +flutter run -d linux + +# macOS +flutter run -d macos +``` + +#### Local Development Server Connection + +When connecting to a local development server: +- **Windows**: Use `http://127.0.0.1:5000/api` as the server URL +- **Android Emulator**: Use `http://10.0.2.2:5000/api` as the server URL + +Configure this in your school key JSON during initial setup. + +## Architecture + +### Project Structure + +The project follows a feature-based architecture: + +``` +lib/ +├── app_utils/ # Application-level utilities +├── common/ # Shared widgets and utilities +├── core/ # Core functionality (DI, session, env) +├── features/ # Feature modules +│ ├── _attendance/ # Attendance management +│ ├── _schoolday_events/ # Schoolday events +│ ├── app_entry_point/ # Login and entry flow +│ ├── app_main_navigation/ # Main navigation +│ ├── app_settings/ # Settings +│ ├── authorizations/ # Authorization management +│ ├── books/ # Library management +│ ├── learning/ # Competence tracking +│ ├── learning_support/ # Learning support plans +│ ├── matrix/ # Matrix integration +│ ├── pupil/ # Pupil profiles and data +│ ├── school/ # School data management +│ ├── school_calendar/ # Calendar management +│ ├── school_lists/ # School lists +│ ├── timetable/ # Timetable management +│ ├── user/ # User management +│ └── workbooks/ # Workbook management +└── l10n/ # Localization files +``` + +Each feature follows a clean architecture pattern: +- `data/` - API services and data access +- `domain/` - Business logic and managers +- `presentation/` - UI components and pages + +### State Management + +The project uses `watch_it` (1.7.0) for dependency injection and state management: +- Dependency injection via `watch_it`'s `di` container +- ValueListenable-based reactive state management +- Watching widgets with `WatchItMixin` + +### Key Technologies + +- **Serverpod Flutter**: 2.9.1 - Backend communication +- **watch_it**: 1.7.0 - Dependency injection and state management +- **flutter_secure_storage**: 10.0.0-beta.1 - Secure local storage +- **encrypt**: 5.0.3 - Data encryption +- **pdf**: 3.11.3 - PDF generation +- **Shorebird**: 2.0.4 - Code push for over-the-air updates + +## Development + +### Code Generation + +Generate JSON serialization code: +```bash +flutter pub run build_runner build --delete-conflicting-outputs +``` + +### Running Tests + +Run all tests: +```bash +flutter test +``` + +### Code Analysis + +Run the analyzer: +```bash +flutter analyze +``` + +### Shorebird Code Push + +The project uses Shorebird for over-the-air (OTA) code push updates, allowing rapid deployment of bug fixes and features without app store releases. + +#### Prerequisites + +1. Install Shorebird CLI: + ```bash + dart pub global activate shorebird_cli + ``` + +2. Login to Shorebird: + ```bash + shorebird login + ``` + +3. Ensure `shorebird.yaml` is configured with your app ID. + +#### Building with Shorebird + +##### Initial Release (First Build) + +When creating the first release of a version, you need to build and release through Shorebird: + +**Android APK:** +```bash +make release_android +``` + +Or manually: +```bash +shorebird release android --artifact=apk +``` + +**Windows:** +```bash +shorebird release windows --release-version=0.5.1+1 +``` + +**iOS (macOS only):** +```bash +shorebird release ios +``` + +The initial release creates a baseline version that patches can be applied to. + +##### Creating Patches + +After an initial release, you can create patches for incremental updates: + +**Android:** +```bash +make patch_android +``` + +Or manually: +```bash +shorebird patch android +``` + +**Windows:** +```bash +make patch_windows +``` + +Or manually: +```bash +shorebird patch windows --platforms=windows --release-version=0.5.1+1 +``` + +**iOS (macOS only):** +```bash +shorebird patch ios +``` + +Patches allow you to update Dart code, assets, and some native code without rebuilding the entire app or going through app store review. + +#### Shorebird Configuration + +The Shorebird configuration is managed in `shorebird.yaml`: +- **app_id**: Unique identifier for your app +- **auto_update**: Controls automatic updates (currently enabled by default) + +See `shorebird.yaml` for current configuration. + +#### Version Management + +When releasing a new version (not a patch), update the version in: +1. `pubspec.yaml` - Update the version number +2. Update the `--release-version` flag in Makefile commands if needed +3. Create a new release (not a patch) for the new version + +#### Build Workflow + +1. **New Version Release**: + - Update version in `pubspec.yaml` + - Build and release: `make release_android` (or platform-specific command) + - This creates a new baseline version + +2. **Incremental Updates**: + - Make code changes + - Create patch: `make patch_android` (or platform-specific command) + - Patches are automatically distributed to users + +#### Limitations + +Shorebird patches have limitations: +- Cannot change native code (iOS/Android native code) +- Cannot change Flutter version +- Cannot change Dart version +- Cannot change signing keys +- Major architectural changes may require a new release + +For changes that patches don't support, create a new release version. + +## Configuration + +### School Keys + +The app requires school keys for initialization. See the main README.md for details on school key format and distribution. + +### Environment Management + +The app supports multiple server environments (development, staging, production) managed through the `EnvManager`. + +### Localization + +Localization files are located in `lib/l10n/`. The project uses Flutter's built-in localization system with ARB files. + +## Known Issues & TODOs + +- **Mail Notifications**: Email notification system implementation +- **Manager Reinitialization**: Handle manager reinitialization when changing instances or logging out +- **Offline Support**: Handle 'no internet connection' scenarios +- **Internationalization**: Expand language support +- **Theme System**: Implement comprehensive theme system +- **Navigation**: Review and improve navigation patterns +- **Shorebird iOS**: iOS support for Shorebird patches (currently Android and Windows supported) + +## Building for Distribution + +All production builds must be created using Shorebird. This enables over-the-air (OTA) updates and is the required build method for distribution. + +### Android + +**Initial Release:** +```bash +make release_android +``` + +Or manually: +```bash +shorebird release android --artifact=apk +``` + +**Patches:** +```bash +make patch_android +``` + +Or manually: +```bash +shorebird patch android +``` + +### Windows + +**Initial Release:** +```bash +shorebird release windows --release-version=0.5.1+1 +``` + +**Patches:** +```bash +make patch_windows +``` + +Or manually: +```bash +shorebird patch windows --platforms=windows --release-version=0.5.1+1 +``` + +### iOS (macOS only) + +**Initial Release:** +```bash +shorebird release ios +``` + +**Patches:** +```bash +shorebird patch ios +``` + +**Note**: Always build releases and patches using Shorebird. Standard Flutter builds are not used for distribution as they lack OTA update capability. + +## Troubleshooting + +### Common Issues + +1. **Missing school_data_hub_client**: Ensure you've run `serverpod generate` in the server directory first. + +2. **Connection Issues**: Verify your server URL in the school key matches your development server address. + +3. **Secure Storage Errors**: Some platforms may require additional permissions. Check platform-specific documentation. + +4. **Shorebird Build Errors**: + - Ensure you're logged in: `shorebird login` + - Verify your `shorebird.yaml` has the correct app_id + - Check that you've created an initial release before trying to create patches + - For version mismatches, ensure the `--release-version` matches your `pubspec.yaml` version + +5. **Shorebird Patch Issues**: + - Patches can only be created from the most recent release + - If you've made changes that patches don't support, create a new release instead + - Ensure your code changes are compatible with the base release version + +## Resources + +- [Flutter Documentation](https://flutter.dev/docs) +- [Serverpod Documentation](https://serverpod.dev) +- [watch_it Package](https://pub.dev/packages/watch_it) +- [Shorebird Documentation](https://shorebird.dev) +- [Main README](../README.md) diff --git a/school_data_hub_flutter/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/school_data_hub_flutter/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png index 19afb6bd..fbd51bc7 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png and b/school_data_hub_flutter/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/school_data_hub_flutter/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png index e3697363..273a931b 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png and b/school_data_hub_flutter/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/school_data_hub_flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png index 75e0019a..0dea2286 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png and b/school_data_hub_flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/school_data_hub_flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png index f019c8ff..d1a6e883 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png and b/school_data_hub_flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/school_data_hub_flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png index 262f69fb..f8fa2aa3 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png and b/school_data_hub_flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/school_data_hub_flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png index 2e5e279d..70d66dd6 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png and b/school_data_hub_flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/school_data_hub_flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png index a01dcc0f..a815c079 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png and b/school_data_hub_flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/school_data_hub_flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png index 0c1d5b80..6dab1cd5 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png and b/school_data_hub_flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/school_data_hub_flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png index ebeea719..169abcf1 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png and b/school_data_hub_flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/school_data_hub_flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/school_data_hub_flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png index 87bf36f7..262eb9c6 100644 Binary files a/school_data_hub_flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png and b/school_data_hub_flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/school_data_hub_flutter/assets/foreground.png b/school_data_hub_flutter/assets/foreground.png index a70c3667..02ddabda 100644 Binary files a/school_data_hub_flutter/assets/foreground.png and b/school_data_hub_flutter/assets/foreground.png differ diff --git a/school_data_hub_flutter/assets/foreground_windows.png b/school_data_hub_flutter/assets/foreground_windows.png index fbb6de6a..0321a4cc 100644 Binary files a/school_data_hub_flutter/assets/foreground_windows.png and b/school_data_hub_flutter/assets/foreground_windows.png differ diff --git a/school_data_hub_flutter/assets/schuldaten_hub_logo.png b/school_data_hub_flutter/assets/schuldaten_hub_logo.png index 79bb6ebb..9706d4e1 100644 Binary files a/school_data_hub_flutter/assets/schuldaten_hub_logo.png and b/school_data_hub_flutter/assets/schuldaten_hub_logo.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 76ffc0a3..877efe36 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index ae20f136..8e1447cf 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index d74d74fd..0bb4c658 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index d0f2bb03..38d16d6e 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index c8c3eac9..723aa065 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index f90ddb2b..d4a9cce9 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 3f8ed390..4f2c1df8 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index d74d74fd..0bb4c658 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c49ef067..32b47769 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index c37ff412..d607b2ba 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index 8138e1dd..fd1957a1 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index f406fa3c..3c41051b 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index 780edfc9..a9220380 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index 12f43619..0a7a36f1 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index c37ff412..d607b2ba 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index c8030f18..bf1964e9 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 63bff8f6..d2016cc9 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index be4b33f9..af7525ab 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 1f6eaf35..f08b6609 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 628e5cb0..804404cf 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0534be15..9960a812 100644 Binary files a/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/school_data_hub_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/school_data_hub_flutter/lib/app_utils/barcode_stream_scanner.dart b/school_data_hub_flutter/lib/app_utils/barcode_stream_scanner.dart index 68d80e5d..7e168046 100644 --- a/school_data_hub_flutter/lib/app_utils/barcode_stream_scanner.dart +++ b/school_data_hub_flutter/lib/app_utils/barcode_stream_scanner.dart @@ -7,8 +7,6 @@ import 'package:school_data_hub_flutter/common/widgets/generic_components/generi import 'package:school_data_hub_flutter/features/pupil/domain/pupil_identity_manager.dart'; import 'package:watch_it/watch_it.dart'; -final _pupilIdentityManager = di(); - class BarcodeStreamScanner extends StatefulWidget { const BarcodeStreamScanner({super.key}); @@ -20,6 +18,7 @@ class _BarcodeStreamScannerState extends State { final MobileScannerController controller = MobileScannerController( torchEnabled: false, ); + final _pupilIdentityManager = di(); int _counter = 0; final List _scannedQrCodes = []; diff --git a/school_data_hub_flutter/lib/app_utils/extensions.dart b/school_data_hub_flutter/lib/app_utils/extensions/datetime_extensions.dart similarity index 74% rename from school_data_hub_flutter/lib/app_utils/extensions.dart rename to school_data_hub_flutter/lib/app_utils/extensions/datetime_extensions.dart index 4a1a8d75..e2702386 100644 --- a/school_data_hub_flutter/lib/app_utils/extensions.dart +++ b/school_data_hub_flutter/lib/app_utils/extensions/datetime_extensions.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -extension DateOnlyCompare on DateTime { +extension DateHubExtension on DateTime { bool isSameDate(DateTime other) { if (year == other.year && month == other.month && day == other.day) { return true; @@ -22,31 +22,24 @@ extension DateOnlyCompare on DateTime { year > other.year; } - String formatForUser() { - final date = this; + String formatDateForUser() { + final date = this.isUtc ? this.toLocal() : this; final DateFormat dateFormat = DateFormat("dd.MM.yyyy"); return dateFormat.format(date).toString(); } - String formatForJson() { + String formatDateForJson() { final DateFormat dateFormat = DateFormat("yyyy-MM-dd"); return dateFormat.format(this).toString(); } /// Format DateTime with time for UI display - String formatWithTimeForUser() { + String formatDateAndTimeForUser() { final date = this.isUtc ? this.toLocal() : this; final DateFormat dateFormat = DateFormat("dd.MM.yyyy HH:mm"); return dateFormat.format(date).toString(); } - /// Format DateTime with time for server communication - String formatWithTimeForJson() { - final date = this.isUtc ? this : this.toUtc(); - final DateFormat dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss"); - return dateFormat.format(date).toString(); - } - /// Format time only for UI display String formatTimeForUser() { final date = this.isUtc ? this.toLocal() : this; @@ -61,13 +54,8 @@ extension DateOnlyCompare on DateTime { return dateFormat.format(date).toString(); } - /// Convert to local time for UI display - DateTime toLocalForUI() { - return this.isUtc ? this.toLocal() : this; - } - /// Convert to UTC for server communication - DateTime toUtcForServer() { + DateTime formatToUtcForServer() { return this.isUtc ? this : this.toUtc(); } @@ -90,17 +78,6 @@ extension DateOnlyCompare on DateTime { } } -extension DisplayAsIsbn on int { - String displayAsIsbn() { - final String isbn = toString(); - return isbn.length == 13 - ? "${isbn.substring(0, 3)}-${isbn.substring(3, 4)}-${isbn.substring(4, 6)}-${isbn.substring(6, 12)}-${isbn.substring(12, 13)}" - : isbn.length == 10 - ? "${isbn.substring(0, 1)}-${isbn.substring(1, 5)}-${isbn.substring(5, 9)}-${isbn.substring(9, 10)}" - : isbn; - } -} - // extension ColorLog on String { // String logWarning() { // final String message = diff --git a/school_data_hub_flutter/lib/app_utils/extensions/isbn_extensions.dart b/school_data_hub_flutter/lib/app_utils/extensions/isbn_extensions.dart new file mode 100644 index 00000000..217cb844 --- /dev/null +++ b/school_data_hub_flutter/lib/app_utils/extensions/isbn_extensions.dart @@ -0,0 +1,10 @@ +extension DisplayAsIsbn on int { + String displayAsIsbn() { + final String isbn = toString(); + return isbn.length == 13 + ? "${isbn.substring(0, 3)}-${isbn.substring(3, 4)}-${isbn.substring(4, 6)}-${isbn.substring(6, 12)}-${isbn.substring(12, 13)}" + : isbn.length == 10 + ? "${isbn.substring(0, 1)}-${isbn.substring(1, 5)}-${isbn.substring(5, 9)}-${isbn.substring(9, 10)}" + : isbn; + } +} diff --git a/school_data_hub_flutter/lib/app_utils/isbn_book_data_scraper.dart b/school_data_hub_flutter/lib/app_utils/isbn_book_data_scraper.dart deleted file mode 100644 index a77e85c2..00000000 --- a/school_data_hub_flutter/lib/app_utils/isbn_book_data_scraper.dart +++ /dev/null @@ -1,70 +0,0 @@ -// import 'dart:typed_data'; - -// import 'package:html/parser.dart' show parse; -// import 'package:http/http.dart' as http; - -// //- TODO: This is experimental code and should be tested before using in production -// class IsbnApiData { -// final Uint8List? image; -// final String imageUrl; -// final String title; -// final String author; -// final String description; -// IsbnApiData( -// {required this.image, -// required this.imageUrl, -// required this.title, -// required this.author, -// required this.description}); -// } - -// Future getIsbnApiData(String isbn) async { -// final cleanIsbn = isbn.replaceAll('-', ''); -// // We check a German isbn resource for the book info -// final imageUrl = "https://buch.isbn.de/cover/$cleanIsbn.jpg"; -// final url = 'https://www.isbn.de/buch/$isbn'; -// final response = await http.get(Uri.parse(url)); -// //debugger(); -// final document = parse(response.body); -// // We scraoe the title, author and description from the html - -// // Extract the title -// var dataElement = document.querySelector('data[itemprop="product-id"]'); -// final title = dataElement?.text ?? '?'; - -// // Extract the author -// var smallElement = document.querySelector('.isbnhead small'); -// final author = smallElement?.text ?? '?'; - -// // Extract the description -// var bookDescElement = document.querySelector('#bookdesc'); -// String description = 'Nicht vorhanden'; -// if (bookDescElement != null) { -// description = bookDescElement.innerHtml -// .replaceAll('
', '') -// .replaceAll(RegExp(r'<[^>]*>'), '') -// .trim(); - -// description = description.replaceAll('\n', ''); - -// // Replace multiple consecutive spaces with a newline -// description = description.replaceAll(RegExp(r' {2,}'), '\n'); -// } -// final image = await http.get(Uri.parse(imageUrl)); -// if (image.statusCode != 200) { -// return IsbnApiData( -// image: null, -// imageUrl: 'https://via.placeholder.com/150', -// title: title, -// author: author, -// description: description); -// } - -// return IsbnApiData( -// image: image.bodyBytes, -// imageUrl: imageUrl, -// title: title, -// author: author, -// description: description.toString()); - -// } diff --git a/school_data_hub_flutter/lib/common/services/pdf_generation_service.dart b/school_data_hub_flutter/lib/common/services/pdf_generation_service.dart index d5ddca96..24d466e1 100644 --- a/school_data_hub_flutter/lib/common/services/pdf_generation_service.dart +++ b/school_data_hub_flutter/lib/common/services/pdf_generation_service.dart @@ -8,7 +8,7 @@ import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_app_bar.dart'; @@ -99,7 +99,7 @@ class AttendancePdfGenerator { // Get the proper directory for saving files final directory = await getApplicationDocumentsDirectory(); final fileName = - "Anwesenheitsliste_${date.formatForUser().replaceAll(' ', '_')}.pdf"; + "Anwesenheitsliste_${date.formatDateForUser().replaceAll(' ', '_')}.pdf"; final file = File('${directory.path}/$fileName'); await file.writeAsBytes(await pdf.save()); @@ -239,7 +239,7 @@ class AttendancePdfGenerator { ), pw.SizedBox(height: 5), pw.Text( - '${_getWeekdayName(date)}, ${date.formatForUser()}', + '${_getWeekdayName(date)}, ${date.formatDateForUser()}', style: pw.TextStyle(fontSize: 12, font: fontRegular), ), ], @@ -249,7 +249,7 @@ class AttendancePdfGenerator { crossAxisAlignment: pw.CrossAxisAlignment.end, children: [ pw.Text( - 'Erstellt am: ${DateTime.now().formatForUser()}', + 'Erstellt am: ${DateTime.now().formatDateForUser()}', style: pw.TextStyle(fontSize: 10, font: fontRegular), ), ], @@ -548,11 +548,11 @@ class AttendancePdfGenerator { mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text( - 'Datum: ${date.formatForUser()}', + 'Datum: ${date.formatDateForUser()}', style: pw.TextStyle(fontSize: 8, font: fontRegular), ), pw.Text( - 'Erstellt am: ${DateTime.now().formatForUser()}', + 'Erstellt am: ${DateTime.now().formatDateForUser()}', style: pw.TextStyle(fontSize: 8, font: fontRegular), ), ], @@ -661,7 +661,7 @@ class MissedSchooldaysPdfGenerator { // Get the proper directory for saving files final directory = await getApplicationDocumentsDirectory(); final fileName = - "Fehlzeitenliste_Detail_${DateTime.now().formatForUser().replaceAll(' ', '_')}.pdf"; + "Fehlzeitenliste_Detail_${DateTime.now().formatDateForUser().replaceAll(' ', '_')}.pdf"; final file = File('${directory.path}/$fileName'); await file.writeAsBytes(await pdf.save()); @@ -980,7 +980,7 @@ class MissedSchooldaysPdfGenerator { ), ), pw.Text( - 'Erstellt am: ${DateTime.now().formatForUser()}', + 'Erstellt am: ${DateTime.now().formatDateForUser()}', style: pw.TextStyle(fontSize: 10, font: fontRegular), ), ], @@ -1097,7 +1097,7 @@ class MissedSchooldaysPdfGenerator { children: [ _buildTableCell(index.toString(), fontRegular, fontBold), _buildTableCell( - date != null ? date.formatForUser() : '-', + date != null ? date.formatDateForUser() : '-', fontRegular, fontBold, ), @@ -1272,7 +1272,7 @@ class MissedSchooldaysPdfGenerator { crossAxisAlignment: pw.CrossAxisAlignment.end, children: [ pw.Text( - 'Erstellt am: ${DateTime.now().formatForUser()}', + 'Erstellt am: ${DateTime.now().formatDateForUser()}', style: pw.TextStyle(fontSize: 10, font: fontRegular), ), ], @@ -1300,7 +1300,7 @@ class MissedSchooldaysPdfGenerator { style: pw.TextStyle(fontSize: 8, font: fontRegular), ), pw.Text( - 'Erstellt am: ${DateTime.now().formatForUser()}', + 'Erstellt am: ${DateTime.now().formatDateForUser()}', style: pw.TextStyle(fontSize: 8, font: fontRegular), ), ], diff --git a/school_data_hub_flutter/lib/common/widgets/dialogs/long_textfield_dialog.dart b/school_data_hub_flutter/lib/common/widgets/dialogs/long_textfield_dialog.dart index 61184912..a44ce9fb 100644 --- a/school_data_hub_flutter/lib/common/widgets/dialogs/long_textfield_dialog.dart +++ b/school_data_hub_flutter/lib/common/widgets/dialogs/long_textfield_dialog.dart @@ -1,15 +1,17 @@ import 'package:flutter/material.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; -Future longTextFieldDialog( - {required String title, - required String? initialValue, - required String labelText, - required BuildContext parentContext}) async { +Future longTextFieldDialog({ + required String title, + required String? initialValue, + required String labelText, + required BuildContext parentContext, +}) async { return await showDialog( - context: parentContext, - builder: (context) { - return StatefulBuilder(builder: (statefulContext, setState) { + context: parentContext, + builder: (context) { + return StatefulBuilder( + builder: (statefulContext, setState) { final TextEditingController textEditingController = TextEditingController(); setState(() { @@ -30,18 +32,19 @@ Future longTextFieldDialog( style: const TextStyle(fontSize: 17), keyboardType: TextInputType.multiline, controller: textEditingController, - decoration: - AppStyles.textFieldDecoration(labelText: labelText), + decoration: AppStyles.textFieldDecoration( + labelText: labelText, + ), ), ), ), ], ), - title: Text(title, - textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.bold, - )), + title: Text( + title, + textAlign: TextAlign.center, + style: const TextStyle(fontWeight: FontWeight.bold), + ), actions: [ Padding( padding: const EdgeInsets.all(5.0), @@ -58,23 +61,23 @@ Future longTextFieldDialog( ), ), ), - initialValue != null - ? Padding( - padding: const EdgeInsets.all(5.0), - child: ElevatedButton( - style: AppStyles.actionButtonStyle, - onPressed: () { - textEditingController.dispose(); - Navigator.of(parentContext).pop(null); - return; - }, // Add onPressed - child: const Text( - "LÖSCHEN", - style: AppStyles.buttonTextStyle, - ), - ), - ) - : const SizedBox.shrink(), + // initialValue != null + // ? Padding( + // padding: const EdgeInsets.all(5.0), + // child: ElevatedButton( + // style: AppStyles.actionButtonStyle, + // onPressed: () { + // textEditingController.dispose(); + // Navigator.of(parentContext).pop(null); + // return; + // }, // Add onPressed + // child: const Text( + // "LÖSCHEN", + // style: AppStyles.buttonTextStyle, + // ), + // ), + // ) + // : const SizedBox.shrink(), Padding( padding: const EdgeInsets.all(5.0), child: ElevatedButton( @@ -89,14 +92,13 @@ Future longTextFieldDialog( textEditingController.dispose(); Navigator.of(parentContext).pop(newSpecialInformation); }, // Add onPressed - child: const Text( - "OK", - style: AppStyles.buttonTextStyle, - ), + child: const Text("OK", style: AppStyles.buttonTextStyle), ), ), ], ); - }); - }); + }, + ); + }, + ); } diff --git a/school_data_hub_flutter/lib/common/widgets/dialogs/schoolday_date_picker.dart b/school_data_hub_flutter/lib/common/widgets/dialogs/schoolday_date_picker.dart index 8ac62191..35e18975 100644 --- a/school_data_hub_flutter/lib/common/widgets/dialogs/schoolday_date_picker.dart +++ b/school_data_hub_flutter/lib/common/widgets/dialogs/schoolday_date_picker.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/features/school_calendar/domain/school_calendar_manager.dart'; import 'package:watch_it/watch_it.dart'; @@ -7,7 +7,9 @@ import 'package:watch_it/watch_it.dart'; final _schoolCalendarManager = di(); Future selectSchooldayDate( - BuildContext context, DateTime thisDate) async { + BuildContext context, + DateTime thisDate, +) async { List availableDates = _schoolCalendarManager.availableDates.value; bool isSelectableSchoolday(DateTime day) { @@ -23,19 +25,20 @@ Future selectSchooldayDate( lastDate: DateTime.now().toUtc().toUtc().add(const Duration(days: 365)), builder: (context, child) { return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: AppColors.backgroundColor, - onPrimary: Color.fromARGB(255, 241, 241, 241), - onSurface: Colors.deepPurple, - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - foregroundColor: AppColors.accentColor, // button text color - ), + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light( + primary: AppColors.backgroundColor, + onPrimary: Color.fromARGB(255, 241, 241, 241), + onSurface: Colors.deepPurple, + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: AppColors.accentColor, // button text color ), ), - child: child!); + ), + child: child!, + ); }, ); if (pickedDate != null) { diff --git a/school_data_hub_flutter/lib/core/di/di_on_user_auth.dart b/school_data_hub_flutter/lib/core/di/di_on_user_auth.dart index 7d998bd2..daf01338 100644 --- a/school_data_hub_flutter/lib/core/di/di_on_user_auth.dart +++ b/school_data_hub_flutter/lib/core/di/di_on_user_auth.dart @@ -144,29 +144,50 @@ class DiInitOnUserAuth { dependsOn: [PupilManager], ); + di.registerSingleton( + FiltersStateManagerImplementation(), + ); + di.registerSingletonWithDependencies( () => LearningSupportFilterManager(), dependsOn: [PupilManager, PupilFilterManager], ); + di.registerSingletonWithDependencies( + () => SchooldayEventManager(), + dependsOn: [SchoolCalendarManager, PupilManager], + ); + di.registerSingletonWithDependencies(() { _log.info('SchooldayEventFilterManager initializing'); return SchooldayEventFilterManager(); - }, dependsOn: [PupilManager, PupilFilterManager]); + }, dependsOn: [PupilManager, PupilFilterManager, SchooldayEventManager]); - di.registerSingletonWithDependencies( - () => PupilsFilterImplementation(di()), - dispose: (instance) => instance.dispose(), - dependsOn: [PupilManager, PupilFilterManager], + di.registerSingletonWithDependencies( + () => AttendanceManager(), + dependsOn: [PupilManager, SchoolCalendarManager], ); - di.registerSingleton( - FiltersStateManagerImplementation(), + di.registerSingletonWithDependencies( + () { + final attendancePupilFilterManager = AttendancePupilFilterManager(); + return attendancePupilFilterManager.init(); + }, + dispose: (instance) => instance.dispose(), + dependsOn: [AttendanceManager], ); - di.registerSingletonWithDependencies( - () => AttendanceManager(), - dependsOn: [SchoolCalendarManager, PupilsFilter], + di.registerSingletonWithDependencies( + () => PupilsFilterImplementation(di()), + dispose: (instance) => instance.dispose(), + dependsOn: [ + PupilManager, + PupilIdentityManager, + PupilFilterManager, + LearningSupportFilterManager, + SchooldayEventFilterManager, + AttendancePupilFilterManager, + ], ); di.registerSingletonAsync(() async { @@ -183,21 +204,8 @@ class DiInitOnUserAuth { return schoolListFilterManager; }, dependsOn: [PupilsFilter, SchoolListManager]); - di.registerSingletonWithDependencies( - () { - final attendancePupilFilterManager = AttendancePupilFilterManager(); - return attendancePupilFilterManager.init(); - }, - dispose: (instance) => instance.dispose(), - dependsOn: [AttendanceManager, PupilsFilter], - ); _log.info('Managers depending on authenticated session initialized'); - di.registerSingletonWithDependencies( - () => SchooldayEventManager(), - dependsOn: [SchoolCalendarManager, PupilsFilter], - ); - di.registerSingletonAsync(() async { _log.info('Registering UserManager'); final userManager = UserManager(); diff --git a/school_data_hub_flutter/lib/core/session/hub_session_manager.dart b/school_data_hub_flutter/lib/core/session/hub_session_manager.dart index c7203cdc..01430d6d 100644 --- a/school_data_hub_flutter/lib/core/session/hub_session_manager.dart +++ b/school_data_hub_flutter/lib/core/session/hub_session_manager.dart @@ -52,6 +52,7 @@ class HubSessionManager with ChangeNotifier { bool get isReady => _isReady; String? get userName => _user?.userInfo?.userName; + bool get isTester => _user?.userFlags.isTester ?? false; int? get userCredit => _user?.credit; diff --git a/school_data_hub_flutter/lib/features/_attendance/domain/attendance_helper_functions.dart b/school_data_hub_flutter/lib/features/_attendance/domain/attendance_helper_functions.dart index ace233af..08951680 100644 --- a/school_data_hub_flutter/lib/features/_attendance/domain/attendance_helper_functions.dart +++ b/school_data_hub_flutter/lib/features/_attendance/domain/attendance_helper_functions.dart @@ -1,7 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/schoolday_date_picker.dart'; import 'package:school_data_hub_flutter/features/_attendance/domain/attendance_manager.dart'; import 'package:school_data_hub_flutter/features/_attendance/domain/models/attendance_values.dart'; @@ -35,20 +35,18 @@ class AttendanceHelper { int missedGlobalSum = 0; final List allPupils = _pupilManager.allPupils; for (PupilProxy pupil in allPupils) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - missedGlobalSum += - missedSchooldays - .where( - (element) => - element.missedType == MissedType.missed || - element.missedType == MissedType.home || - element.returned == true, - ) - .length; + missedGlobalSum += missedSchooldays + .where( + (element) => + element.missedType == MissedType.missed || + element.missedType == MissedType.home || + element.returned == true, + ) + .length; } } return missedGlobalSum; @@ -58,20 +56,18 @@ class AttendanceHelper { int unexcusedGlobalSum = 0; final List allPupils = _pupilManager.allPupils; for (PupilProxy pupil in allPupils) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - unexcusedGlobalSum += - missedSchooldays - .where( - (element) => - element.missedType == MissedType.missed && - element.unexcused == true, - ) - .length; + unexcusedGlobalSum += missedSchooldays + .where( + (element) => + element.missedType == MissedType.missed && + element.unexcused == true, + ) + .length; } } return unexcusedGlobalSum; @@ -81,16 +77,14 @@ class AttendanceHelper { int lateGlobalSum = 0; final List allPupils = _pupilManager.allPupils; for (PupilProxy pupil in allPupils) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - lateGlobalSum += - missedSchooldays - .where((element) => element.missedType == MissedType.late) - .length; + lateGlobalSum += missedSchooldays + .where((element) => element.missedType == MissedType.late) + .length; } } return lateGlobalSum; @@ -100,16 +94,14 @@ class AttendanceHelper { int contactedGlobalSum = 0; final List allPupils = _pupilManager.allPupils; for (PupilProxy pupil in allPupils) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - contactedGlobalSum += - missedSchooldays - .where((element) => element.contacted != ContactedType.notSet) - .length; + contactedGlobalSum += missedSchooldays + .where((element) => element.contacted != ContactedType.notSet) + .length; } } return contactedGlobalSum; @@ -119,15 +111,13 @@ class AttendanceHelper { int pickedUpGlobalSum = 0; final List allPupils = _pupilManager.allPupils; for (PupilProxy pupil in allPupils) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - pickedUpGlobalSum += - missedSchooldays - .where((element) => element.returned == true) - .length; + pickedUpGlobalSum += missedSchooldays + .where((element) => element.returned == true) + .length; } } return pickedUpGlobalSum; @@ -142,10 +132,9 @@ class AttendanceHelper { List missedPupils = []; if (filteredPupils.isNotEmpty) { for (PupilProxy pupil in filteredPupils) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.any( (missedSchoolday) => missedSchoolday.schoolday!.schoolday.isSameDate(thisDate) && @@ -168,10 +157,9 @@ class AttendanceHelper { List unexcusedPupils = []; for (PupilProxy pupil in filteredPupils) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.any( (missedSchoolday) => missedSchoolday.schoolday!.schoolday.isSameDate(thisDate) && @@ -231,19 +219,17 @@ class AttendanceHelper { static int missedclassExcusedSum(PupilProxy pupil) { // count the number of missed classes - avoid null when missedSchooldays is empty int missedclassCount = 0; - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - missedclassCount = - missedSchooldays - .where( - (element) => - element.missedType == MissedType.missed && - element.unexcused == false, - ) - .length; + missedclassCount = missedSchooldays + .where( + (element) => + element.missedType == MissedType.missed && + element.unexcused == false, + ) + .length; } return missedclassCount; } @@ -251,77 +237,69 @@ class AttendanceHelper { static int missedclassUnexcusedSum(PupilProxy pupil) { // count the number of unexcused missed classes int missedclassCount = 0; - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - missedclassCount = - missedSchooldays - .where( - (element) => - element.missedType == MissedType.missed && - element.unexcused == true, - ) - .length; + missedclassCount = missedSchooldays + .where( + (element) => + element.missedType == MissedType.missed && + element.unexcused == true, + ) + .length; } return missedclassCount; } static int lateSum(PupilProxy pupil) { int lateCount = 0; - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - lateCount = - missedSchooldays - .where((element) => element.missedType == MissedType.late) - .length; + lateCount = missedSchooldays + .where((element) => element.missedType == MissedType.late) + .length; } return lateCount; } static int lateUnexcusedSum(PupilProxy pupil) { int missedSchooldayUnexcusedCount = 0; - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isNotEmpty) { - missedSchooldayUnexcusedCount = - missedSchooldays - .where( - (element) => - element.missedType == MissedType.late && - element.unexcused == true, - ) - .length; + missedSchooldayUnexcusedCount = missedSchooldays + .where( + (element) => + element.missedType == MissedType.late && + element.unexcused == true, + ) + .length; } return missedSchooldayUnexcusedCount; } static int contactedSum(PupilProxy pupil) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; - int contactedCount = - missedSchooldays - .where((element) => element.contacted != ContactedType.notSet) - .length; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; + int contactedCount = missedSchooldays + .where((element) => element.contacted != ContactedType.notSet) + .length; return contactedCount; } static int goneHomeSum(PupilProxy pupil) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; - int goneHomeCount = - missedSchooldays.where((element) => element.returned == true).length; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; + int goneHomeCount = missedSchooldays + .where((element) => element.returned == true) + .length; return goneHomeCount; } @@ -329,10 +307,9 @@ class AttendanceHelper { //- check condition functions static bool pupilIsMissedToday(PupilProxy pupil) { - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; if (missedSchooldays.isEmpty) return false; if (missedSchooldays.any( (element) => @@ -414,10 +391,9 @@ class AttendanceHelper { // The function returns absence hours and unexcused hours for the current semester // (for grades 3 and 4) // in the last semester for the school year (for grades 1 and 2) - final missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + final missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; // if no missed classes, we return 0, 0 if (missedSchooldays.isEmpty) { return (missed: 0, unexcused: 0); @@ -434,17 +410,13 @@ class AttendanceHelper { ) ?? schoolSemesters.last; - final List missedSchooldaysThisSemester = - missedSchooldays - .where( - (missedSchoolday) => - isMissedSchooldayinSemester( - missedSchoolday, - currentSemester, - ) && - missedSchoolday.missedType == MissedType.missed, - ) - .toList(); + final List missedSchooldaysThisSemester = missedSchooldays + .where( + (missedSchoolday) => + isMissedSchooldayinSemester(missedSchoolday, currentSemester) && + missedSchoolday.missedType == MissedType.missed, + ) + .toList(); final List unexcusedMissedSchooldayesThisSemester = missedSchooldaysThisSemester .where((missedSchoolday) => missedSchoolday.unexcused == true) diff --git a/school_data_hub_flutter/lib/features/_attendance/domain/attendance_manager.dart b/school_data_hub_flutter/lib/features/_attendance/domain/attendance_manager.dart index 5d87cfd0..10e9a922 100644 --- a/school_data_hub_flutter/lib/features/_attendance/domain/attendance_manager.dart +++ b/school_data_hub_flutter/lib/features/_attendance/domain/attendance_manager.dart @@ -4,7 +4,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; import 'package:school_data_hub_flutter/features/_attendance/data/attendance_api_service.dart'; @@ -15,20 +15,17 @@ import 'package:school_data_hub_flutter/features/school_calendar/domain/school_c import 'package:watch_it/watch_it.dart'; class AttendanceManager with ChangeNotifier { - final _pupilManager = di(); - - final _schoolCalendarManager = di(); - - final _notificationService = di(); - - final _sessionManager = di(); + // Lazy getters to avoid accessing dependencies during construction + PupilManager get _pupilManager => di(); + SchoolCalendarManager get _schoolCalendarManager => + di(); + NotificationService get _notificationService => di(); + HubSessionManager get _sessionManager => di(); + Client get _client => di(); final _log = Logger('AttendanceManager'); - final _attendanceApiService = AttendanceApiService(); - final _client = di(); - StreamSubscription? _missedSchooldaySubscription; final ValueNotifier> _missedSchooldays = ValueNotifier( @@ -65,7 +62,9 @@ class AttendanceManager with ChangeNotifier { MissedSchoolday? getPupilMissedSchooldayOnDate(int pupilId, DateTime date) { return _pupilMissedSchooldaysMap[pupilId]!.missedSchooldays .firstWhereOrNull( - (element) => element.schoolday!.schoolday.isSameDate(date), + (element) => element.schoolday!.schoolday.isSameDate( + date.formatToUtcForServer(), + ), ); } @@ -232,8 +231,8 @@ class AttendanceManager with ChangeNotifier { //- CRUD operantions void fetchAllPupilMissedSchooldayes() async { - final fetchedMissedSchooldayes = - await _attendanceApiService.fetchAllMissedSchooldayes(); + final fetchedMissedSchooldayes = await _attendanceApiService + .fetchAllMissedSchooldayes(); if (fetchedMissedSchooldayes == null) return; _updateMissedSchooldayesInCollections(fetchedMissedSchooldayes); } diff --git a/school_data_hub_flutter/lib/features/_attendance/domain/filters/attendance_pupil_filter.dart b/school_data_hub_flutter/lib/features/_attendance/domain/filters/attendance_pupil_filter.dart index 9691f29f..d870a951 100644 --- a/school_data_hub_flutter/lib/features/_attendance/domain/filters/attendance_pupil_filter.dart +++ b/school_data_hub_flutter/lib/features/_attendance/domain/filters/attendance_pupil_filter.dart @@ -1,7 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; import 'package:school_data_hub_flutter/features/_attendance/domain/attendance_manager.dart'; import 'package:school_data_hub_flutter/features/_attendance/domain/models/enums.dart'; diff --git a/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/attendance_list_page.dart b/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/attendance_list_page.dart index 79dea4c7..6efe8f0a 100644 --- a/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/attendance_list_page.dart +++ b/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/attendance_list_page.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:logging/logging.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_sliver_list.dart'; @@ -58,8 +58,9 @@ class AttendanceListPage extends WatchingWidget { value.cancel(); }, ); - DateTime thisDate = - watchValue((SchoolCalendarManager x) => x.thisDate).toLocal(); + DateTime thisDate = watchValue( + (SchoolCalendarManager x) => x.thisDate, + ).toLocal(); callOnce((context) { _attendanceManager.fetchMissedSchooldayesOnASchoolday(thisDate); @@ -80,15 +81,14 @@ class AttendanceListPage extends WatchingWidget { children: [ Icon( Icons.today_rounded, - color: - AttendanceHelper.schooldayIsToday(thisDate) - ? const Color.fromARGB(255, 83, 196, 55) - : Colors.white, + color: AttendanceHelper.schooldayIsToday(thisDate) + ? const Color.fromARGB(255, 83, 196, 55) + : Colors.white, size: 30, ), const Gap(10), Text( - '${thisDate.asWeekdayName(context)}, ${thisDate.formatForUser()}', + '${thisDate.asWeekdayName(context)}, ${thisDate.formatDateForUser()}', style: const TextStyle( fontSize: 25, color: Colors.white, @@ -101,9 +101,8 @@ class AttendanceListPage extends WatchingWidget { automaticallyImplyLeading: false, ), body: RefreshIndicator( - onRefresh: - () async => - _attendanceManager.fetchMissedSchooldayesOnASchoolday(thisDate), + onRefresh: () async => + _attendanceManager.fetchMissedSchooldayesOnASchoolday(thisDate), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 600), diff --git a/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/atendance_list_card.dart b/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/atendance_list_card.dart index 113845e4..3c9762a8 100644 --- a/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/atendance_list_card.dart +++ b/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/atendance_list_card.dart @@ -4,7 +4,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/long_textfield_dialog.dart'; @@ -37,12 +37,13 @@ class AttendanceCard extends WatchingWidget { final missedSchooldaysList = _attendanceManager .getPupilMissedSchooldaysProxy(pupil.pupilId); - final MissedSchoolday? missedSchoolday = watch( - missedSchooldaysList, - ).missedSchooldays.firstWhereOrNull( - (element) => - element.schoolday?.schoolday.isSameDate(thisDate.toLocal()) ?? false, - ); + final MissedSchoolday? missedSchoolday = watch(missedSchooldaysList) + .missedSchooldays + .firstWhereOrNull( + (element) => + element.schoolday?.schoolday.isSameDate(thisDate.toLocal()) ?? + false, + ); AttendanceValues attendanceInfo = AttendanceHelper.getAttendanceValues( missedSchoolday, @@ -74,8 +75,8 @@ class AttendanceCard extends WatchingWidget { AvatarWithBadges(pupil: pupil, size: 80), Expanded( child: GestureDetector( - onLongPress: - () => createMissedSchooldayList(context, pupil), + onLongPress: () => + createMissedSchooldayList(context, pupil), onTap: () { Navigator.of(context).push( MaterialPageRoute( @@ -102,10 +103,8 @@ class AttendanceCard extends WatchingWidget { ); Navigator.of(context).pushReplacement( MaterialPageRoute( - builder: - (ctx) => PupilProfilePage( - pupil: pupil, - ), + builder: (ctx) => + PupilProfilePage(pupil: pupil), ), ); }, @@ -192,39 +191,42 @@ class AttendanceCard extends WatchingWidget { ContactedType.notSet || attendanceInfo.returnedValue == true ? DropdownButtonHideUnderline( - child: DropdownButton( - icon: const Visibility( - visible: false, - child: Icon(Icons.arrow_downward), + child: DropdownButton( + icon: const Visibility( + visible: false, + child: Icon(Icons.arrow_downward), + ), + onTap: () { + FocusManager.instance.primaryFocus! + .unfocus(); + }, + value: + attendanceInfo.contactedTypeValue, + items: dropdownContactedMenuItems, + onChanged: (newValue) { + if (attendanceInfo + .contactedTypeValue == + newValue || + attendanceInfo.unexcusedValue == + false) { + return; + } + _attendanceManager + .updateContactedValue( + pupil.pupilId, + newValue!, + thisDate, + ); + }, ), - onTap: () { - FocusManager.instance.primaryFocus! - .unfocus(); - }, - value: attendanceInfo.contactedTypeValue, - items: dropdownContactedMenuItems, - onChanged: (newValue) { - if (attendanceInfo.contactedTypeValue == - newValue || - attendanceInfo.unexcusedValue == - false) { - return; - } - _attendanceManager.updateContactedValue( - pupil.pupilId, - newValue!, - thisDate, - ); - }, - ), - ) + ) : Container( - height: 45, - width: 30, - decoration: const BoxDecoration( - color: Colors.white, + height: 45, + width: 30, + decoration: const BoxDecoration( + color: Colors.white, + ), ), - ), const Gap(4), Checkbox( checkColor: Colors.white, @@ -274,17 +276,17 @@ class AttendanceCard extends WatchingWidget { child: Center( child: attendanceInfo.createdOrModifiedByValue != - null - ? Text( - attendanceInfo - .createdOrModifiedByValue!, - textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ) - : const SizedBox.shrink(), + null + ? Text( + attendanceInfo + .createdOrModifiedByValue!, + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ) + : const SizedBox.shrink(), ), ), Row( @@ -428,8 +430,8 @@ class AttendanceCard extends WatchingWidget { AvatarWithBadges(pupil: pupil, size: 80), Expanded( child: GestureDetector( - onLongPress: - () => createMissedSchooldayList(context, pupil), + onLongPress: () => + createMissedSchooldayList(context, pupil), onTap: () { Navigator.of(context).push( MaterialPageRoute( @@ -456,9 +458,8 @@ class AttendanceCard extends WatchingWidget { ); Navigator.of(context).pushReplacement( MaterialPageRoute( - builder: - (ctx) => - PupilProfilePage(pupil: pupil), + builder: (ctx) => + PupilProfilePage(pupil: pupil), ), ); }, @@ -559,18 +560,18 @@ class AttendanceCard extends WatchingWidget { child: Center( child: attendanceInfo - .createdOrModifiedByValue != - null - ? Text( - attendanceInfo - .createdOrModifiedByValue!, - textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ) - : const SizedBox.shrink(), + .createdOrModifiedByValue != + null + ? Text( + attendanceInfo + .createdOrModifiedByValue!, + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ) + : const SizedBox.shrink(), ), ), ], @@ -624,42 +625,42 @@ class AttendanceCard extends WatchingWidget { ContactedType.notSet || attendanceInfo.returnedValue == true ? DropdownButtonHideUnderline( - child: DropdownButton( - icon: const Visibility( - visible: false, - child: Icon(Icons.arrow_downward), + child: DropdownButton( + icon: const Visibility( + visible: false, + child: Icon(Icons.arrow_downward), + ), + onTap: () { + FocusManager.instance.primaryFocus! + .unfocus(); + }, + value: + attendanceInfo.contactedTypeValue, + items: dropdownContactedMenuItems, + onChanged: (newValue) { + if (attendanceInfo + .contactedTypeValue == + newValue || + attendanceInfo.unexcusedValue == + false) { + return; + } + _attendanceManager + .updateContactedValue( + pupil.pupilId, + newValue!, + thisDate, + ); + }, ), - onTap: () { - FocusManager.instance.primaryFocus! - .unfocus(); - }, - value: - attendanceInfo.contactedTypeValue, - items: dropdownContactedMenuItems, - onChanged: (newValue) { - if (attendanceInfo - .contactedTypeValue == - newValue || - attendanceInfo.unexcusedValue == - false) { - return; - } - _attendanceManager - .updateContactedValue( - pupil.pupilId, - newValue!, - thisDate, - ); - }, - ), - ) + ) : Container( - height: 48, - width: 30, - decoration: const BoxDecoration( - color: Colors.white, + height: 48, + width: 30, + decoration: const BoxDecoration( + color: Colors.white, + ), ), - ), const Gap(2), Container( width: 20.0, diff --git a/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/attendance_filters.dart b/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/attendance_filters.dart index 844531e3..47db4da3 100644 --- a/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/attendance_filters.dart +++ b/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/attendance_filters.dart @@ -32,9 +32,9 @@ class AttendanceFilters extends WatchingWidget { (PupilFilterManager x) => x.pupilFilterState, ); - bool valueOgs = activePupilFilters[PupilFilter.ogs]!; + bool valueOgs = activePupilFilters[PupilFilter.afterSchoolCare]!; - bool valueNotOgs = activePupilFilters[PupilFilter.notOgs]!; + bool valueNotOgs = activePupilFilters[PupilFilter.noAfterSchoolCare]!; return Column( children: [ @@ -159,15 +159,17 @@ class AttendanceFilters extends WatchingWidget { _pupilFilterLocator.setPupilFilter( pupilFilterRecords: [ - (filter: PupilFilter.notOgs, value: false), - (filter: PupilFilter.ogs, value: val), + (filter: PupilFilter.noAfterSchoolCare, value: false), + (filter: PupilFilter.afterSchoolCare, value: val), ], ); return; } _pupilFilterLocator.setPupilFilter( - pupilFilterRecords: [(filter: PupilFilter.ogs, value: val)], + pupilFilterRecords: [ + (filter: PupilFilter.afterSchoolCare, value: val), + ], ); }, ), @@ -179,15 +181,15 @@ class AttendanceFilters extends WatchingWidget { // in case not ogs is selected, ogs should be deselected _pupilFilterLocator.setPupilFilter( pupilFilterRecords: [ - (filter: PupilFilter.ogs, value: false), - (filter: PupilFilter.notOgs, value: val), + (filter: PupilFilter.afterSchoolCare, value: false), + (filter: PupilFilter.noAfterSchoolCare, value: val), ], ); return; } _pupilFilterLocator.setPupilFilter( pupilFilterRecords: [ - (filter: PupilFilter.notOgs, value: val), + (filter: PupilFilter.noAfterSchoolCare, value: val), ], ); }, diff --git a/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/dialogues/multiple_entries_dialog.dart b/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/dialogues/multiple_entries_dialog.dart index 6acd4db0..f7c383c4 100644 --- a/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/dialogues/multiple_entries_dialog.dart +++ b/school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/dialogues/multiple_entries_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/schoolday_date_picker.dart'; @@ -19,166 +19,197 @@ final _schoolCalendarManager = di(); // based on https://mobikul.com/creating-stateful-dialog-form-in-flutter/ Future createMissedSchooldayList( - BuildContext context, PupilProxy pupil) async { + BuildContext context, + PupilProxy pupil, +) async { final DateTime thisDate = _schoolCalendarManager.thisDate.value; return await showDialog( - context: context, - builder: (context) { - // final schoolCalendarManager = di(); - // schoolCalendarManager.setStartDate(thisDate); - // schoolCalendarManager.setEndDate(thisDate); - MissedType dialogdropdownValue = MissedType.missed; - DateTime startDate = thisDate; - DateTime endDate = thisDate; + context: context, + builder: (context) { + // final schoolCalendarManager = di(); + // schoolCalendarManager.setStartDate(thisDate); + // schoolCalendarManager.setEndDate(thisDate); + MissedType dialogdropdownValue = MissedType.missed; + DateTime startDate = thisDate; + DateTime endDate = thisDate; - return StatefulBuilder(builder: (context, setState) { + return StatefulBuilder( + builder: (context, setState) { return AlertDialog( content: Form( - key: _missedDatesformKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: DropdownButtonHideUnderline( - child: DropdownButton( - onTap: () { - FocusManager.instance.primaryFocus!.unfocus(); - }, - value: dialogdropdownValue, - items: [ - DropdownMenuItem( - value: MissedType.missed, - child: Container( - width: 40.0, - height: 40.0, - decoration: BoxDecoration( - color: Colors.orange[300], - shape: BoxShape.circle, - ), - child: const Center( - child: Text("F", - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 24, - )), - ), - )), - DropdownMenuItem( - value: MissedType.home, - child: Container( - width: 40.0, - height: 40.0, - decoration: const BoxDecoration( - color: Colors.lightBlue, - shape: BoxShape.circle, - ), - child: const Center( - child: Text("H", - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 24, - )), - ), - )), - ], - onChanged: (newvalue) { - setState(() { - dialogdropdownValue = newvalue!; - }); - }), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('von', - style: TextStyle( - fontSize: 22, fontWeight: FontWeight.bold)), - const Gap(15), - InkWell( - onTap: () async { - final DateTime? date = - await selectSchooldayDate(context, thisDate); - if (date != null) { - setState(() { - startDate = date; - }); - } - }, - child: Text( - startDate.formatForUser(), - style: const TextStyle( - color: AppColors.interactiveColor, - fontWeight: FontWeight.bold, - fontSize: 22, + key: _missedDatesformKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: DropdownButtonHideUnderline( + child: DropdownButton( + onTap: () { + FocusManager.instance.primaryFocus!.unfocus(); + }, + value: dialogdropdownValue, + items: [ + DropdownMenuItem( + value: MissedType.missed, + child: Container( + width: 40.0, + height: 40.0, + decoration: BoxDecoration( + color: Colors.orange[300], + shape: BoxShape.circle, ), - )), - const Gap(5), - IconButton( - onPressed: () async { - final DateTime? date = - await selectSchooldayDate(context, thisDate); - if (date != null) { - setState(() { - startDate = date; - }); - } - }, - icon: const Icon( - Icons.calendar_today, - color: AppColors.interactiveColor, - )), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('bis', - style: TextStyle( - fontSize: 22, fontWeight: FontWeight.bold)), - const Gap(15), - InkWell( - onTap: () async { - final DateTime? date = - await selectSchooldayDate(context, thisDate); - if (date != null) { - setState(() { - endDate = date; - }); - } - }, - child: Text( - endDate.formatForUser(), - style: const TextStyle( - color: AppColors.interactiveColor, - fontWeight: FontWeight.bold, - fontSize: 22, + child: const Center( + child: Text( + "F", + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + ), + ), + ), + DropdownMenuItem( + value: MissedType.home, + child: Container( + width: 40.0, + height: 40.0, + decoration: const BoxDecoration( + color: Colors.lightBlue, + shape: BoxShape.circle, + ), + child: const Center( + child: Text( + "H", + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), ), - )), - const Gap(5), - IconButton( - onPressed: () async { - final DateTime? date = - await selectSchooldayDate(context, thisDate); - if (date != null) { - setState(() { - endDate = date; - }); - } - }, - icon: const Icon( - Icons.calendar_today, - color: AppColors.interactiveColor, - )), - ], + ), + ), + ], + onChanged: (newvalue) { + setState(() { + dialogdropdownValue = newvalue!; + }); + }, + ), ), - ], - )), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'von', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + const Gap(15), + InkWell( + onTap: () async { + final DateTime? date = await selectSchooldayDate( + context, + thisDate, + ); + if (date != null) { + setState(() { + startDate = date; + }); + } + }, + child: Text( + startDate.formatDateForUser(), + style: const TextStyle( + color: AppColors.interactiveColor, + fontWeight: FontWeight.bold, + fontSize: 22, + ), + ), + ), + const Gap(5), + IconButton( + onPressed: () async { + final DateTime? date = await selectSchooldayDate( + context, + thisDate, + ); + if (date != null) { + setState(() { + startDate = date; + }); + } + }, + icon: const Icon( + Icons.calendar_today, + color: AppColors.interactiveColor, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'bis', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + const Gap(15), + InkWell( + onTap: () async { + final DateTime? date = await selectSchooldayDate( + context, + thisDate, + ); + if (date != null) { + setState(() { + endDate = date; + }); + } + }, + child: Text( + endDate.formatDateForUser(), + style: const TextStyle( + color: AppColors.interactiveColor, + fontWeight: FontWeight.bold, + fontSize: 22, + ), + ), + ), + const Gap(5), + IconButton( + onPressed: () async { + final DateTime? date = await selectSchooldayDate( + context, + thisDate, + ); + if (date != null) { + setState(() { + endDate = date; + }); + } + }, + icon: const Icon( + Icons.calendar_today, + color: AppColors.interactiveColor, + ), + ), + ], + ), + ], + ), + ), title: const Text( 'Mehrere Einträge', style: TextStyle(fontWeight: FontWeight.bold), @@ -208,22 +239,22 @@ Future createMissedSchooldayList( onPressed: () { if (_missedDatesformKey.currentState!.validate()) { _attendanceManager.postManyMissedSchooldayes( - pupil.internalId, - startDate, - endDate, - dialogdropdownValue); + pupil.internalId, + startDate, + endDate, + dialogdropdownValue, + ); _missedDatesformKey.currentState!.reset(); Navigator.of(context).pop(); } }, // Add onPressed - child: const Text( - "OK", - style: AppStyles.buttonTextStyle, - ), + child: const Text("OK", style: AppStyles.buttonTextStyle), ), ), ], ); - }); - }); + }, + ); + }, + ); } diff --git a/school_data_hub_flutter/lib/features/_attendance/presentation/missed_classes_pupil_list_page/missed_classes_pupil_list_page.dart b/school_data_hub_flutter/lib/features/_attendance/presentation/missed_classes_pupil_list_page/missed_classes_pupil_list_page.dart index 3179de27..62132934 100644 --- a/school_data_hub_flutter/lib/features/_attendance/presentation/missed_classes_pupil_list_page/missed_classes_pupil_list_page.dart +++ b/school_data_hub_flutter/lib/features/_attendance/presentation/missed_classes_pupil_list_page/missed_classes_pupil_list_page.dart @@ -22,7 +22,9 @@ class MissedSchooldayesPupilListPage extends WatchingWidget { return Scaffold( backgroundColor: AppColors.canvasColor, appBar: const GenericAppBar( - iconData: Icons.calendar_month_rounded, title: 'Fehlzeiten'), + iconData: Icons.calendar_month_rounded, + title: 'Fehlzeiten', + ), body: RefreshIndicator( onRefresh: () async => di().fetchAllPupils(), child: Center( @@ -36,9 +38,9 @@ class MissedSchooldayesPupilListPage extends WatchingWidget { title: AttendanceRankingListSearchbar(pupils: pupils), ), GenericSliverListWithEmptyListCheck( - items: pupils, - itemBuilder: (_, pupil) => - AttendanceRankingListCard(pupil)), + items: pupils, + itemBuilder: (_, pupil) => MissedClassesPupilListCard(pupil), + ), ], ), ), diff --git a/school_data_hub_flutter/lib/features/_attendance/presentation/missed_classes_pupil_list_page/widgets/missed_classes_pupil_list_card.dart b/school_data_hub_flutter/lib/features/_attendance/presentation/missed_classes_pupil_list_page/widgets/missed_classes_pupil_list_card.dart index 9bb245bc..bdce91c3 100644 --- a/school_data_hub_flutter/lib/features/_attendance/presentation/missed_classes_pupil_list_page/widgets/missed_classes_pupil_list_card.dart +++ b/school_data_hub_flutter/lib/features/_attendance/presentation/missed_classes_pupil_list_page/widgets/missed_classes_pupil_list_card.dart @@ -14,16 +14,17 @@ import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/avat import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/pupil_profile_attendance_content.dart'; import 'package:watch_it/watch_it.dart'; -class AttendanceRankingListCard extends WatchingStatefulWidget { +class MissedClassesPupilListCard extends WatchingStatefulWidget { final PupilProxy pupil; - const AttendanceRankingListCard(this.pupil, {super.key}); + const MissedClassesPupilListCard(this.pupil, {super.key}); @override - State createState() => + State createState() => _AttendanceRankingListCardState(); } -class _AttendanceRankingListCardState extends State { +class _AttendanceRankingListCardState + extends State { late CustomExpansionTileController _tileController; @override void initState() { @@ -41,8 +42,12 @@ class _AttendanceRankingListCardState extends State { surfaceTintColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), elevation: 1.0, - margin: - const EdgeInsets.only(left: 4.0, right: 4.0, top: 4.0, bottom: 4.0), + margin: const EdgeInsets.only( + left: 4.0, + right: 4.0, + top: 4.0, + bottom: 4.0, + ), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -69,14 +74,16 @@ class _AttendanceRankingListCardState extends State { onTap: () { di() .setPupilProfileNavPage( - ProfileNavigationState - .attendance.value); - Navigator.of(context) - .push(MaterialPageRoute( - builder: (ctx) => PupilProfilePage( - pupil: pupil, + ProfileNavigationState + .attendance + .value, + ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => + PupilProfilePage(pupil: pupil), ), - )); + ); }, child: Row( children: [ @@ -119,12 +126,12 @@ class _AttendanceRankingListCardState extends State { child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: CustomExpansionTileSwitch( - customExpansionTileController: - _tileController, - includeSwitch: true, - switchColor: AppColors.interactiveColor, - expansionSwitchWidget: - attendanceStats(pupil)), + customExpansionTileController: + _tileController, + includeSwitch: true, + switchColor: AppColors.interactiveColor, + expansionSwitchWidget: attendanceStats(pupil), + ), ), ), const Gap(10), @@ -174,9 +181,10 @@ class _AttendanceRankingListCardState extends State { ], ), CustomExpansionTileContent( - title: null, - tileController: _tileController, - widgetList: [PupilAttendanceContent(pupil: pupil)]), + title: null, + tileController: _tileController, + widgetList: [PupilAttendanceContent(pupil: pupil)], + ), ], ), ); diff --git a/school_data_hub_flutter/lib/features/_schoolday_events/data/schoolday_event_api_service.dart b/school_data_hub_flutter/lib/features/_schoolday_events/data/schoolday_event_api_service.dart index 613ae29e..cceae58d 100644 --- a/school_data_hub_flutter/lib/features/_schoolday_events/data/schoolday_event_api_service.dart +++ b/school_data_hub_flutter/lib/features/_schoolday_events/data/schoolday_event_api_service.dart @@ -3,10 +3,11 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/domain/models/nullable_records.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/pupil_manager.dart'; import 'package:watch_it/watch_it.dart'; final _client = di(); @@ -18,20 +19,27 @@ class SchooldayEventApiService { //- post schooldayEvent Future postSchooldayEvent( + String pupilName, int pupilId, int schooldayId, + DateTime dateTime, SchooldayEventType type, String reason, ) async { final userName = _hubSessionManager.userName!; _notificationService.apiRunning(true); + final pupil = di().getPupilByPupilId(pupilId); + final tutor = pupil?.groupTutor; try { final event = await _client.schooldayEvent.createSchooldayEvent( + pupilNameAndGroup: pupilName, + dateTimeAsString: dateTime.formatDateAndTimeForUser(), pupilId: pupilId, schooldayId: schooldayId, type: type, reason: reason, createdBy: userName, + tutor: tutor ?? '', ); _notificationService.apiRunning(false); @@ -74,14 +82,15 @@ class SchooldayEventApiService { NullableDateTimeRecord? processedAt, int? schooldayId, }) async { - bool changedProcessedToFalse = false; + bool changedProcessedStatus = false; // if the schooldayEvent is patched as processed, // processing user and processed date are automatically added if (processed == true && processedBy == null && processedAt == null) { processedBy = (value: _hubSessionManager.user!.userInfo!.userName!); - processedAt = (value: DateTime.now().toUtcForServer()); + processedAt = (value: DateTime.now().formatToUtcForServer()); + changedProcessedStatus = true; } // if the schooldayEvent is patched as not processed, @@ -90,7 +99,7 @@ class SchooldayEventApiService { if (processed == false) { processedBy = (value: null); processedAt = (value: null); - changedProcessedToFalse = true; + changedProcessedStatus = true; } final schooldayEventToUpdate = schooldayEvent.copyWith( createdBy: createdBy ?? schooldayEvent.createdBy, @@ -98,18 +107,24 @@ class SchooldayEventApiService { eventReason: reason ?? schooldayEvent.eventReason, schooldayId: schooldayId ?? schooldayEvent.schooldayId, processed: processed ?? schooldayEvent.processed, - processedBy: - processedBy != null ? processedBy.value : schooldayEvent.processedBy, - processedAt: - processedAt != null ? processedAt.value : schooldayEvent.processedAt, + processedBy: processedBy != null + ? processedBy.value + : schooldayEvent.processedBy, + processedAt: processedAt != null + ? processedAt.value + : schooldayEvent.processedAt, ); - + final pupil = di().getPupilByPupilId(schooldayEvent.pupilId)!; try { _notificationService.apiRunning(true); final updatedSchooldayEvent = await _client.schooldayEvent .updateSchooldayEvent( schooldayEventToUpdate, - changedProcessedToFalse, + changedProcessedStatus, + '${pupil.firstName} (${pupil.group})', + '${pupil.groupTutor}', + '${di().userName!}', + '${DateTime.now().formatDateAndTimeForUser()}', ); _notificationService.apiRunning(false); return updatedSchooldayEvent; diff --git a/school_data_hub_flutter/lib/features/_schoolday_events/domain/filters/schoolday_event_filter_manager.dart b/school_data_hub_flutter/lib/features/_schoolday_events/domain/filters/schoolday_event_filter_manager.dart index 75934a81..d5059868 100644 --- a/school_data_hub_flutter/lib/features/_schoolday_events/domain/filters/schoolday_event_filter_manager.dart +++ b/school_data_hub_flutter/lib/features/_schoolday_events/domain/filters/schoolday_event_filter_manager.dart @@ -2,26 +2,30 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; -import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; import 'package:school_data_hub_flutter/features/_schoolday_events/domain/models/schoolday_event_enums.dart'; +import 'package:school_data_hub_flutter/features/_schoolday_events/domain/schoolday_event_manager.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; import 'package:watch_it/watch_it.dart'; -final _filtersStateManager = di(); - -final _pupilsFilter = di(); - typedef SchooldayEventFilterRecord = ({ SchooldayEventFilter filter, - bool value + bool value, }); class SchooldayEventFilterManager { + // Lazy getters to avoid circular dependency issues during initialization + FiltersStateManager get _filtersStateManager => di(); + PupilsFilter get _pupilsFilter => di(); + SchooldayEventManager get _schooldayEventManager => + di(); + final _schooldayEventsFilterState = ValueNotifier>( - initialSchooldayEventFilterValues); + initialSchooldayEventFilterValues, + ); ValueListenable> - get schooldayEventsFilterState => _schooldayEventsFilterState; + get schooldayEventsFilterState => _schooldayEventsFilterState; final _pupilIdsWithFilteredSchooldayEvents = ValueNotifier>({}); ValueListenable> get pupilIdsWithFilteredSchooldayEvents => @@ -29,14 +33,17 @@ class SchooldayEventFilterManager { SchooldayEventFilterManager(); - resetFilters() { + void resetFilters() { _schooldayEventsFilterState.value = {...initialSchooldayEventFilterValues}; _filtersStateManager.setFilterState( - filterState: FilterState.schooldayEvent, value: false); + filterState: FilterState.schooldayEvent, + value: false, + ); } - void setFilter( - {required List schooldayEventFilters}) { + void setFilter({ + required List schooldayEventFilters, + }) { for (SchooldayEventFilterRecord record in schooldayEventFilters) { _schooldayEventsFilterState.value = { ..._schooldayEventsFilterState.value, @@ -45,18 +52,25 @@ class SchooldayEventFilterManager { } final schooldayEventFiltesStateEqualsInitialValues = const MapEquality() - .equals(_schooldayEventsFilterState.value, - initialSchooldayEventFilterValues); + .equals( + _schooldayEventsFilterState.value, + initialSchooldayEventFilterValues, + ); _filtersStateManager.setFilterState( - filterState: FilterState.schooldayEvent, - value: !schooldayEventFiltesStateEqualsInitialValues); + filterState: FilterState.schooldayEvent, + value: !schooldayEventFiltesStateEqualsInitialValues, + ); + + // Filter the schoolday events and populate the pupil IDs set + filteredSchooldayEvents(_schooldayEventManager.schooldayEvents); _pupilsFilter.refreshs(); } List filteredSchooldayEvents( - List schooldayEvents) { + List schooldayEvents, + ) { List filteredSchooldayEvents = []; Set filteredPupilIds = {}; @@ -152,8 +166,9 @@ class SchooldayEventFilterManager { complementaryFilter = false; if (activeFilters[SchooldayEventFilter.violenceAgainstPupils]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.violenceAgainstPupils.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.violenceAgainstPupils.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -162,8 +177,9 @@ class SchooldayEventFilterManager { } if (activeFilters[SchooldayEventFilter.violenceAgainstAdults]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.violenceAgainstTeachers.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.violenceAgainstTeachers.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -171,8 +187,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.violenceAgainstThings]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.violenceAgainstThings.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.violenceAgainstThings.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -180,8 +197,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.insultOthers]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.insultOthers.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.insultOthers.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -189,8 +207,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.annoy]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.annoyOthers.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.annoyOthers.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -198,8 +217,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.dangerousBehaviour]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.dangerousBehaviour.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.dangerousBehaviour.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -207,8 +227,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.disturbLesson]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.disturbLesson.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.disturbLesson.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -216,8 +237,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.ignoreInstructions]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.ignoreInstructions.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.ignoreInstructions.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -225,8 +247,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.learningDevelopmentInfo]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.learningDevelopmentInfo.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.learningDevelopmentInfo.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -234,8 +257,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.learningSupportInfo]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.learningSupportInfo.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.learningSupportInfo.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -243,8 +267,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.admonitionInfo]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.admonitionInfo.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.admonitionInfo.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -252,8 +277,9 @@ class SchooldayEventFilterManager { } } if (activeFilters[SchooldayEventFilter.other]!) { - if (schooldayEvent.eventReason - .contains(SchooldayEventReason.other.value)) { + if (schooldayEvent.eventReason.contains( + SchooldayEventReason.other.value, + )) { isMatched = true; complementaryFilter = true; } else if (!complementaryFilter) { @@ -271,11 +297,14 @@ class SchooldayEventFilterManager { if (filterIsActive) { _filtersStateManager.setFilterState( - filterState: FilterState.schooldayEvent, value: true); + filterState: FilterState.schooldayEvent, + value: true, + ); } // sort schooldayEvents, latest first filteredSchooldayEvents.sort( - (a, b) => b.schoolday!.schoolday.compareTo(a.schoolday!.schoolday)); + (a, b) => b.schoolday!.schoolday.compareTo(a.schoolday!.schoolday), + ); _pupilIdsWithFilteredSchooldayEvents.value = filteredPupilIds; return filteredSchooldayEvents; } diff --git a/school_data_hub_flutter/lib/features/_schoolday_events/domain/schoolday_event_helper_functions.dart b/school_data_hub_flutter/lib/features/_schoolday_events/domain/schoolday_event_helper_functions.dart index 1ba47a07..b66a255d 100644 --- a/school_data_hub_flutter/lib/features/_schoolday_events/domain/schoolday_event_helper_functions.dart +++ b/school_data_hub_flutter/lib/features/_schoolday_events/domain/schoolday_event_helper_functions.dart @@ -1,5 +1,5 @@ import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; @@ -21,12 +21,13 @@ class SchooldayEventsCounts { final int totalSentHomeSchooldayEvents; final int totalParentsMeetingSchooldayEvents; - SchooldayEventsCounts( - {required this.totalSchooldayEvents, - required this.totalLessonSchooldayEvents, - required this.totalOgsSchooldayEvents, - required this.totalSentHomeSchooldayEvents, - required this.totalParentsMeetingSchooldayEvents}); + SchooldayEventsCounts({ + required this.totalSchooldayEvents, + required this.totalLessonSchooldayEvents, + required this.totalOgsSchooldayEvents, + required this.totalSentHomeSchooldayEvents, + required this.totalParentsMeetingSchooldayEvents, + }); } class SchoolDayEventHelper { @@ -35,11 +36,13 @@ class SchoolDayEventHelper { int pupilsWithEvents = 0; for (PupilProxy pupil in pupils) { if (_schooldayEventFilterManager - .filteredSchooldayEvents(_schooldayEventManager - .getPupilSchooldayEventsProxy(pupil.pupilId) - .schooldayEvents - .values - .toList()) + .filteredSchooldayEvents( + _schooldayEventManager + .getPupilSchooldayEventsProxy(pupil.pupilId) + .schooldayEvents + .values + .toList(), + ) .isNotEmpty) { pupilsWithEvents++; } @@ -49,11 +52,13 @@ class SchoolDayEventHelper { static int schooldayEventSum(PupilProxy pupil) { return _schooldayEventFilterManager - .filteredSchooldayEvents(_schooldayEventManager - .getPupilSchooldayEventsProxy(pupil.pupilId) - .schooldayEvents - .values - .toList()) + .filteredSchooldayEvents( + _schooldayEventManager + .getPupilSchooldayEventsProxy(pupil.pupilId) + .schooldayEvents + .values + .toList(), + ) .length; } @@ -68,11 +73,13 @@ class SchoolDayEventHelper { static DateTime getPupilLastSchooldayEventDate(PupilProxy pupil) { final List schooldayEvents = _schooldayEventFilterManager - .filteredSchooldayEvents(_schooldayEventManager - .getPupilSchooldayEventsProxy(pupil.pupilId) - .schooldayEvents - .values - .toList()); + .filteredSchooldayEvents( + _schooldayEventManager + .getPupilSchooldayEventsProxy(pupil.pupilId) + .schooldayEvents + .values + .toList(), + ); if (schooldayEvents.isEmpty) { // TODO: Watch out - why did we use this date? // if schoolday events is empty, we return a mock date @@ -83,7 +90,8 @@ class SchoolDayEventHelper { static int? findSchooldayEventIndex(PupilProxy pupil, DateTime date) { final int? foundSchooldayEventIndex = pupil.schooldayEvents?.indexWhere( - (datematch) => (datematch.schoolday!.schoolday.isSameDate(date))); + (datematch) => (datematch.schoolday!.schoolday.isSameDate(date)), + ); if (foundSchooldayEventIndex == null) { return null; } @@ -95,18 +103,21 @@ class SchoolDayEventHelper { .getPupilSchooldayEventsProxy(pupil.pupilId) .schooldayEvents; if (pupilSchooldayEvents.isEmpty) return false; - if (pupilSchooldayEvents.values.any((element) => - element.schoolday!.schoolday.isSameDate(DateTime.now()) && - (element.eventType == SchooldayEventType.admonition || - element.eventType == SchooldayEventType.afternoonCareAdmonition || - element.eventType == SchooldayEventType.admonitionAndBanned))) { + if (pupilSchooldayEvents.values.any( + (element) => + element.schoolday!.schoolday.isSameDate(DateTime.now()) && + (element.eventType == SchooldayEventType.admonition || + element.eventType == SchooldayEventType.afternoonCareAdmonition || + element.eventType == SchooldayEventType.admonitionAndBanned), + )) { return true; } return false; } static SchooldayEventsCounts getSchooldayEventsCounts( - List pupils) { + List pupils, + ) { int totalSchooldayEvents = 0; int teachingSchooldayEvents = 0; int ogsSchooldayEvents = 0; @@ -115,47 +126,62 @@ class SchoolDayEventHelper { for (PupilProxy pupil in pupils) { final pupilSchooldayEvents = _schooldayEventFilterManager - .filteredSchooldayEvents(_schooldayEventManager - .getPupilSchooldayEventsProxy(pupil.pupilId) - .schooldayEvents - .values - .toList()); + .filteredSchooldayEvents( + _schooldayEventManager + .getPupilSchooldayEventsProxy(pupil.pupilId) + .schooldayEvents + .values + .toList(), + ); totalSchooldayEvents = totalSchooldayEvents + pupilSchooldayEvents.length; - teachingSchooldayEvents = teachingSchooldayEvents + + teachingSchooldayEvents = + teachingSchooldayEvents + pupilSchooldayEvents - .where((element) => - element.eventType == SchooldayEventType.admonition) + .where( + (element) => element.eventType == SchooldayEventType.admonition, + ) .length; - ogsSchooldayEvents = ogsSchooldayEvents + + ogsSchooldayEvents = + ogsSchooldayEvents + pupilSchooldayEvents - .where((element) => - element.eventType == - SchooldayEventType.afternoonCareAdmonition) + .where( + (element) => + element.eventType == + SchooldayEventType.afternoonCareAdmonition, + ) .length; - sentHomeSchooldayEvents = sentHomeSchooldayEvents + + sentHomeSchooldayEvents = + sentHomeSchooldayEvents + pupilSchooldayEvents - .where((element) => - element.eventType == SchooldayEventType.admonitionAndBanned) + .where( + (element) => + element.eventType == SchooldayEventType.admonitionAndBanned, + ) .length; - parentsMeetingSchooldayEvents = parentsMeetingSchooldayEvents + + parentsMeetingSchooldayEvents = + parentsMeetingSchooldayEvents + pupilSchooldayEvents - .where((element) => - element.eventType == SchooldayEventType.parentsMeeting) + .where( + (element) => + element.eventType == SchooldayEventType.parentsMeeting, + ) .length; } return SchooldayEventsCounts( - totalSchooldayEvents: totalSchooldayEvents, - totalLessonSchooldayEvents: teachingSchooldayEvents, - totalOgsSchooldayEvents: ogsSchooldayEvents, - totalSentHomeSchooldayEvents: sentHomeSchooldayEvents, - totalParentsMeetingSchooldayEvents: parentsMeetingSchooldayEvents); + totalSchooldayEvents: totalSchooldayEvents, + totalLessonSchooldayEvents: teachingSchooldayEvents, + totalOgsSchooldayEvents: ogsSchooldayEvents, + totalSentHomeSchooldayEvents: sentHomeSchooldayEvents, + totalParentsMeetingSchooldayEvents: parentsMeetingSchooldayEvents, + ); } static DateTime getLastSchoolEventDate(List schooldayEvents) { schooldayEvents.sort( - (a, b) => b.schoolday!.schoolday.compareTo(a.schoolday!.schoolday)); + (a, b) => b.schoolday!.schoolday.compareTo(a.schoolday!.schoolday), + ); return schooldayEvents.first.schoolday!.schoolday; } @@ -174,7 +200,7 @@ class SchoolDayEventHelper { return schooldayEvents; } -//- TODO: this should use SchooldavEventType enum + //- TODO: this should use SchooldavEventType enum static String getSchooldayEventTypeText(SchooldayEventType value) { switch (value) { @@ -258,19 +284,21 @@ class SchoolDayEventHelper { (b.schooldayEvents?.isEmpty ?? true) ? compareLastSchooldayEventDates(a, b) // Handle empty or both empty : (a.schooldayEvents?.isEmpty ?? true) - ? 1 - : -1; // Place empty after non-empty + ? 1 + : -1; // Place empty after non-empty } static int comparePupilsByLastNonProcessedSchooldayEvent( - PupilProxy a, PupilProxy b) { + PupilProxy a, + PupilProxy b, + ) { // Handle potential null cases with null-aware operators return (a.schooldayEvents?.isEmpty ?? true) == (b.schooldayEvents?.isEmpty ?? true) ? compareLastSchooldayEventDates(a, b) // Handle empty or both empty : (a.schooldayEvents?.isEmpty ?? true) - ? 1 - : -1; // Place empty after non-empty + ? 1 + : -1; // Place empty after non-empty } static int compareLastSchooldayEventDates(PupilProxy a, PupilProxy b) { @@ -278,8 +306,9 @@ class SchoolDayEventHelper { if (a.schooldayEvents!.isNotEmpty && b.schooldayEvents!.isNotEmpty) { final schooldayEventA = a.schooldayEvents!.last.schoolday!.schoolday; final schooldayEventB = b.schooldayEvents!.last.schoolday!.schoolday; - return schooldayEventB - .compareTo(schooldayEventA); // Reversed for descending order + return schooldayEventB.compareTo( + schooldayEventA, + ); // Reversed for descending order } else { return 0; } diff --git a/school_data_hub_flutter/lib/features/_schoolday_events/domain/schoolday_event_manager.dart b/school_data_hub_flutter/lib/features/_schoolday_events/domain/schoolday_event_manager.dart index 36aea19f..3881273b 100644 --- a/school_data_hub_flutter/lib/features/_schoolday_events/domain/schoolday_event_manager.dart +++ b/school_data_hub_flutter/lib/features/_schoolday_events/domain/schoolday_event_manager.dart @@ -102,11 +102,19 @@ class SchooldayEventManager with ChangeNotifier { Future postSchooldayEvent( int pupilId, int schooldayId, + DateTime dateTime, SchooldayEventType type, String reason, ) async { - final SchooldayEvent schooldayEvent = await _schooldayEventApiService - .postSchooldayEvent(pupilId, schooldayId, type, reason); + final SchooldayEvent + schooldayEvent = await _schooldayEventApiService.postSchooldayEvent( + '${di().getPupilByPupilId(pupilId)!.firstName} (${di().getPupilByPupilId(pupilId)!.group})', + pupilId, + schooldayId, + dateTime, + type, + reason, + ); _updateSchooldayEventCollections(schooldayEvent); _notificationService.showSnackBar( @@ -121,8 +129,8 @@ class SchooldayEventManager with ChangeNotifier { Future fetchSchooldayEvents() async { try { - final List events = - await _schooldayEventApiService.fetchSchooldayEvents(); + final List events = await _schooldayEventApiService + .fetchSchooldayEvents(); updateSchooldayEventsBatchInCollections(events); } catch (e) { diff --git a/school_data_hub_flutter/lib/features/_schoolday_events/presentation/new_schoolday_event_page/new_schoolday_event_page.dart b/school_data_hub_flutter/lib/features/_schoolday_events/presentation/new_schoolday_event_page/new_schoolday_event_page.dart index f225e7cb..5bb66768 100644 --- a/school_data_hub_flutter/lib/features/_schoolday_events/presentation/new_schoolday_event_page/new_schoolday_event_page.dart +++ b/school_data_hub_flutter/lib/features/_schoolday_events/presentation/new_schoolday_event_page/new_schoolday_event_page.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/information_dialog.dart'; @@ -126,6 +126,7 @@ class NewSchooldayEventPage extends WatchingWidget { await _schooldayEventManager.postSchooldayEvent( pupilId, schoolday!.id!, + thisDate, schooldayEventType, schooldayEventReasons, ); @@ -193,26 +194,24 @@ class NewSchooldayEventPage extends WatchingWidget { onChanged: (SchooldayEventType? newValue) { schooldayEventTypeDropdown.value = newValue!; }, - items: - SchooldayEventType.values - .map>(( - SchooldayEventType value, - ) { - return DropdownMenuItem( - value: value, - child: Text( - _getDropdownItemText(value), - style: TextStyle( - color: - value == SchooldayEventType.notSet - ? Colors.red - : AppColors.backgroundColor, - fontSize: 20, - ), - ), - ); - }) - .toList(), + items: SchooldayEventType.values + .map>(( + SchooldayEventType value, + ) { + return DropdownMenuItem( + value: value, + child: Text( + _getDropdownItemText(value), + style: TextStyle( + color: value == SchooldayEventType.notSet + ? Colors.red + : AppColors.backgroundColor, + fontSize: 20, + ), + ), + ); + }) + .toList(), ), const Gap(10), const Row( @@ -255,7 +254,7 @@ class NewSchooldayEventPage extends WatchingWidget { } }, child: Text( - thisDate.value.formatForUser(), + watch(thisDate).value.formatDateForUser(), style: AppStyles.title.copyWith( color: AppColors.interactiveColor, ), @@ -270,170 +269,170 @@ class NewSchooldayEventPage extends WatchingWidget { ), const Gap(5), Expanded( - child: - schooldayEventType == SchooldayEventType.notSet - ? const Center( - child: Text( - 'Bitte eine Ereignis-Art auswählen!', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ) - : schooldayEventType == - SchooldayEventType.parentsMeeting - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Wrap( - children: [ - SchooldayEventReasonFilterChip( - isReason: - watch(learningDevelopmentInfo).value, - onSelected: (value) { - learningDevelopmentInfo.value = value; - }, - emojis: '💡🧠', - text: 'Lernentwicklung', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: - watch(learningSupportInfo).value, - onSelected: (value) { - learningSupportInfo.value = value; - }, - emojis: '🛟🧠', - text: 'Förderung', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: watch(admonitionInfo).value, - onSelected: (value) { - admonitionInfo.value = value; - }, - emojis: '⚠️ℹ️', - text: 'Regelverstoß', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: watch(other).value, - onSelected: (value) { - other.value = value; - }, - emojis: '📝', - text: 'Sonstiges', - ), - const Gap(5), - ], - ), - ], - ), - ) - : SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Wrap( - children: [ - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: - watch(violenceAgainstPupils).value, - onSelected: (value) { - violenceAgainstPupils.value = value; - }, - emojis: '🤜🤕', - text: 'Gewalt gegen Kinder', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: - watch(violenceAgainstTeacher).value, - onSelected: (value) { - violenceAgainstTeacher.value = value; - }, - emojis: '🤜🎓️', - text: 'Gewalt gegen Erwachsene', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: - watch(violenceAgainstThings).value, - onSelected: (value) { - violenceAgainstThings.value = value; - }, - emojis: '🤜🏫', - text: 'Gewalt gegen Sachen', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: watch(insultOthers).value, - onSelected: (value) { - insultOthers.value = value; - }, - emojis: '🤬💔', - text: 'Beleidigen', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: watch(annoyOthers).value, - onSelected: (value) { - annoyOthers.value = value; - }, - emojis: '😈😖', - text: 'Ärgern', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: watch(imminentDanger).value, - onSelected: (value) { - imminentDanger.value = value; - }, - emojis: '🚨😱', - text: 'Gefahr für sich/andere', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: - watch( - ignoreTeacherInstructions, - ).value, - onSelected: (value) { - ignoreTeacherInstructions.value = value; - }, - emojis: '🎓️🙉', - text: 'Anweisungen ignorieren', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: watch(disturbLesson).value, - onSelected: (value) { - disturbLesson.value = value; - }, - emojis: '🛑🎓️', - text: 'Unterricht stören', - ), - const Gap(5), - SchooldayEventReasonFilterChip( - isReason: watch(other).value, - onSelected: (value) { - other.value = value; - }, - emojis: '📝', - text: 'Sonstiges', - ), - const Gap(5), - ], - ), - ], + child: schooldayEventType == SchooldayEventType.notSet + ? const Center( + child: Text( + 'Bitte eine Ereignis-Art auswählen!', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), + textAlign: TextAlign.center, + ), + ) + : schooldayEventType == SchooldayEventType.parentsMeeting + ? SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + children: [ + SchooldayEventReasonFilterChip( + isReason: watch( + learningDevelopmentInfo, + ).value, + onSelected: (value) { + learningDevelopmentInfo.value = value; + }, + emojis: '💡🧠', + text: 'Lernentwicklung', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch(learningSupportInfo).value, + onSelected: (value) { + learningSupportInfo.value = value; + }, + emojis: '🛟🧠', + text: 'Förderung', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch(admonitionInfo).value, + onSelected: (value) { + admonitionInfo.value = value; + }, + emojis: '⚠️ℹ️', + text: 'Regelverstoß', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch(other).value, + onSelected: (value) { + other.value = value; + }, + emojis: '📝', + text: 'Sonstiges', + ), + const Gap(5), + ], + ), + ], ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + children: [ + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch( + violenceAgainstPupils, + ).value, + onSelected: (value) { + violenceAgainstPupils.value = value; + }, + emojis: '🤜🤕', + text: 'Gewalt gegen Kinder', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch( + violenceAgainstTeacher, + ).value, + onSelected: (value) { + violenceAgainstTeacher.value = value; + }, + emojis: '🤜🎓️', + text: 'Gewalt gegen Erwachsene', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch( + violenceAgainstThings, + ).value, + onSelected: (value) { + violenceAgainstThings.value = value; + }, + emojis: '🤜🏫', + text: 'Gewalt gegen Sachen', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch(insultOthers).value, + onSelected: (value) { + insultOthers.value = value; + }, + emojis: '🤬💔', + text: 'Beleidigen', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch(annoyOthers).value, + onSelected: (value) { + annoyOthers.value = value; + }, + emojis: '😈😖', + text: 'Ärgern', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch(imminentDanger).value, + onSelected: (value) { + imminentDanger.value = value; + }, + emojis: '🚨😱', + text: 'Gefahr für sich/andere', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch( + ignoreTeacherInstructions, + ).value, + onSelected: (value) { + ignoreTeacherInstructions.value = value; + }, + emojis: '🎓️🙉', + text: 'Anweisungen ignorieren', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch(disturbLesson).value, + onSelected: (value) { + disturbLesson.value = value; + }, + emojis: '🛑🎓️', + text: 'Unterricht stören', + ), + const Gap(5), + SchooldayEventReasonFilterChip( + isReason: watch(other).value, + onSelected: (value) { + other.value = value; + }, + emojis: '📝', + text: 'Sonstiges', + ), + const Gap(5), + ], + ), + ], + ), + ), ), const Gap(10), ElevatedButton( diff --git a/school_data_hub_flutter/lib/features/_schoolday_events/presentation/schoolday_event_list_page/widgets/pupil_schoolday_event_card.dart b/school_data_hub_flutter/lib/features/_schoolday_events/presentation/schoolday_event_list_page/widgets/pupil_schoolday_event_card.dart index 8058e79f..4413671d 100644 --- a/school_data_hub_flutter/lib/features/_schoolday_events/presentation/schoolday_event_list_page/widgets/pupil_schoolday_event_card.dart +++ b/school_data_hub_flutter/lib/features/_schoolday_events/presentation/schoolday_event_list_page/widgets/pupil_schoolday_event_card.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; @@ -38,27 +38,23 @@ class PupilSchooldayEventCard extends StatelessWidget { final isAdmin = _hubSessionManager.isAdmin; return Card( - color: - !schooldayEvent.processed - ? AppColors.notProcessedColor - : AppColors.cardInCardColor, + color: !schooldayEvent.processed + ? AppColors.notProcessedColor + : AppColors.cardInCardColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), child: Container( decoration: BoxDecoration( - border: - !schooldayEvent.processed - ? Border.all( - color: - Colors - .orangeAccent, // Specify the color of the border here - width: 3, // Specify the width of the border here - ) - : Border.all( - color: - AppColors - .cardInCardBorderColor, // Specify the color of the border here - width: 2, - ), + border: !schooldayEvent.processed + ? Border.all( + color: Colors + .orangeAccent, // Specify the color of the border here + width: 3, // Specify the width of the border here + ) + : Border.all( + color: AppColors + .cardInCardBorderColor, // Specify the color of the border here + width: 2, + ), borderRadius: BorderRadius.circular(10), ), child: Padding( @@ -78,52 +74,49 @@ class PupilSchooldayEventCard extends StatelessWidget { children: [ isAuthorized ? InkWell( - onTap: () async { - DateTime? date = - await selectSchooldayDate( - context, + onTap: () async { + DateTime? + date = await selectSchooldayDate( + context, + _schoolCalendarManager.thisDate.value, + ); + if (date == null) return; + final schooldayId = _schoolCalendarManager - .thisDate - .value, - ); - if (date == null) return; - final schooldayId = - _schoolCalendarManager - .getSchooldayByDate(date) - ?.id; + .getSchooldayByDate(date) + ?.id; - await _schooldayEventManager - .updateSchooldayEvent( - eventToUpdate: schooldayEvent, + await _schooldayEventManager + .updateSchooldayEvent( + eventToUpdate: schooldayEvent, - schooldayId: schooldayId, - ); - _notificationService.showSnackBar( - NotificationType.success, - 'Ereignis als bearbeitet markiert!', - ); - }, - child: Text( - schooldayEvent.schoolday!.schoolday - .formatForUser(), + schooldayId: schooldayId, + ); + _notificationService.showSnackBar( + NotificationType.success, + 'Ereignis als bearbeitet markiert!', + ); + }, + child: Text( + schooldayEvent.schoolday!.schoolday + .formatDateForUser(), + style: const TextStyle( + color: AppColors.interactiveColor, + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ) + : Text( + schooldayEvent.schoolday!.schoolday + .formatDateForUser(), style: const TextStyle( - color: AppColors.interactiveColor, + color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20, ), ), - ) - : Text( - schooldayEvent.schoolday!.schoolday - .toLocalForUI() - .formatForUser(), - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), const Gap(5), InkWell( onLongPress: () { @@ -175,40 +168,40 @@ class PupilSchooldayEventCard extends StatelessWidget { // only admin can change the admonishing user isAdmin ? InkWell( - onTap: () async { - final String? createdBy = - await shortTextfieldDialog( - context: context, - title: 'Erstellt von:', - labelText: 'Kürzel eingeben', - hintText: 'Kürzel eingeben', - obscureText: false, - ); - if (createdBy != null) { - await _schooldayEventManager - .updateSchooldayEvent( - eventToUpdate: schooldayEvent, - createdBy: createdBy, - processed: false, + onTap: () async { + final String? createdBy = + await shortTextfieldDialog( + context: context, + title: 'Erstellt von:', + labelText: 'Kürzel eingeben', + hintText: 'Kürzel eingeben', + obscureText: false, ); - } - }, - child: Text( + if (createdBy != null) { + await _schooldayEventManager + .updateSchooldayEvent( + eventToUpdate: schooldayEvent, + createdBy: createdBy, + processed: false, + ); + } + }, + child: Text( + schooldayEvent.createdBy, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: AppColors.backgroundColor, + ), + ), + ) + : Text( schooldayEvent.createdBy, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, - color: AppColors.backgroundColor, ), ), - ) - : Text( - schooldayEvent.createdBy, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), const Gap(10), ], ), @@ -260,24 +253,22 @@ class PupilSchooldayEventCard extends StatelessWidget { 'Dokument gelöscht!', ); }, - child: - schooldayEvent.processedDocumentId != null - ? DocumentImage( - documentId: - schooldayEvent - .processedDocument! - .documentId, - size: 70, - ) - : SizedBox( - height: 70, - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Image.asset( - 'assets/document_camera.png', - ), + child: schooldayEvent.processedDocumentId != null + ? DocumentImage( + documentId: schooldayEvent + .processedDocument! + .documentId, + size: 70, + ) + : SizedBox( + height: 70, + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.asset( + 'assets/document_camera.png', ), ), + ), ), ], ), @@ -322,22 +313,20 @@ class PupilSchooldayEventCard extends StatelessWidget { 'Dokument gelöscht!', ); }, - child: - schooldayEvent.document != null - ? DocumentImage( - documentId: - schooldayEvent.document!.documentId, - size: 70, - ) - : SizedBox( - height: 70, - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Image.asset( - 'assets/document_camera.png', - ), + child: schooldayEvent.document != null + ? DocumentImage( + documentId: schooldayEvent.document!.documentId, + size: 70, + ) + : SizedBox( + height: 70, + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.asset( + 'assets/document_camera.png', ), ), + ), ), ], ), @@ -395,77 +384,77 @@ class PupilSchooldayEventCard extends StatelessWidget { if (schooldayEvent.processedBy != null) isAdmin ? InkWell( - onTap: () async { - //-TODO: get the user from select user page - final String? processingUser = - await shortTextfieldDialog( - context: context, - title: 'Bearbeitet von:', - labelText: 'Kürzel eingeben', - hintText: 'Kürzel eingeben', - obscureText: false, - ); - if (processingUser != null) { - await _schooldayEventManager - .updateSchooldayEvent( - eventToUpdate: schooldayEvent, - processedBy: ( - value: processingUser, - ), + onTap: () async { + //-TODO: get the user from select user page + final String? processingUser = + await shortTextfieldDialog( + context: context, + title: 'Bearbeitet von:', + labelText: 'Kürzel eingeben', + hintText: 'Kürzel eingeben', + obscureText: false, ); - } - }, - child: Text( + if (processingUser != null) { + await _schooldayEventManager + .updateSchooldayEvent( + eventToUpdate: schooldayEvent, + processedBy: ( + value: processingUser, + ), + ); + } + }, + child: Text( + schooldayEvent.processedBy!, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.interactiveColor, + ), + ), + ) + : Text( schooldayEvent.processedBy!, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: AppColors.interactiveColor, ), ), - ) - : Text( - schooldayEvent.processedBy!, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), if (schooldayEvent.processedAt != null) const Gap(10), if (schooldayEvent.processedAt != null) _hubSessionManager.isAdmin ? InkWell( - onTap: () async { - final DateTime? newDate = - await selectSchooldayDate( - context, - DateTime.now(), - ); - - if (newDate != null) { - await _schooldayEventManager - .updateSchooldayEvent( - eventToUpdate: schooldayEvent, - processedAt: (value: newDate), + onTap: () async { + final DateTime? newDate = + await selectSchooldayDate( + context, + DateTime.now(), ); - } - }, - child: Text( - 'am ${schooldayEvent.processedAt!.toLocalForUI().formatForUser()}', + + if (newDate != null) { + await _schooldayEventManager + .updateSchooldayEvent( + eventToUpdate: schooldayEvent, + processedAt: (value: newDate), + ); + } + }, + child: Text( + 'am ${schooldayEvent.processedAt!.formatDateForUser()}', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: AppColors.interactiveColor, + ), + ), + ) + : Text( + 'am ${schooldayEvent.processedAt!.formatDateForUser()}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, - color: AppColors.interactiveColor, ), ), - ) - : Text( - 'am ${schooldayEvent.processedAt!.toLocalForUI().formatForUser()}', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), ], ), ), diff --git a/school_data_hub_flutter/lib/features/_schoolday_events/presentation/schoolday_event_list_page/widgets/schoolday_event_pupil_list_card/schoolday_event_pupil_list_card.dart b/school_data_hub_flutter/lib/features/_schoolday_events/presentation/schoolday_event_list_page/widgets/schoolday_event_pupil_list_card/schoolday_event_pupil_list_card.dart index 272e0af3..8fa9eaf4 100644 --- a/school_data_hub_flutter/lib/features/_schoolday_events/presentation/schoolday_event_list_page/widgets/schoolday_event_pupil_list_card/schoolday_event_pupil_list_card.dart +++ b/school_data_hub_flutter/lib/features/_schoolday_events/presentation/schoolday_event_list_page/widgets/schoolday_event_pupil_list_card/schoolday_event_pupil_list_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile_content.dart'; @@ -45,10 +45,11 @@ class _SchooldayEventListCardState extends State { Widget build(BuildContext context) { final PupilProxy pupil = widget.passedPupil; final unfilteredEvents = watch( - _schooldayEventManager.getPupilSchooldayEventsProxy(pupil.pupilId)) - .schooldayEvents; - schooldayEvents = _schooldayEventFilterManager - .filteredSchooldayEvents(unfilteredEvents.values.toList()); + _schooldayEventManager.getPupilSchooldayEventsProxy(pupil.pupilId), + ).schooldayEvents; + schooldayEvents = _schooldayEventFilterManager.filteredSchooldayEvents( + unfilteredEvents.values.toList(), + ); // TODO: This is a workaround for the filter manager. It should be moved to // - SchooldayEventListPage or to the filter manager. if (_schooldayEventFilterManager.schooldayEventsFilterState.value.values @@ -58,124 +59,131 @@ class _SchooldayEventListCardState extends State { } } return Card( - color: Colors.white, - surfaceTintColor: Colors.white, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - children: [ - AvatarWithBadges(pupil: pupil, size: 80), - const Gap(5), - Expanded( - child: Column( - children: [ - const Gap(10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: InkWell( - onTap: () { - _mainMenuBottomNavManager - .setPupilProfileNavPage(4); - Navigator.of(context).push(MaterialPageRoute( - builder: (ctx) => PupilProfilePage( - pupil: pupil, - ), - )); - }, - child: Text( - pupil.firstName, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold), + color: Colors.white, + surfaceTintColor: Colors.white, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + AvatarWithBadges(pupil: pupil, size: 80), + const Gap(5), + Expanded( + child: Column( + children: [ + const Gap(10), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: InkWell( + onTap: () { + _mainMenuBottomNavManager + .setPupilProfileNavPage(4); + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => + PupilProfilePage(pupil: pupil), + ), + ); + }, + child: Text( + pupil.firstName, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), ), ), - ) - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: InkWell( - onTap: () { - // Navigator.of(context).push(MaterialPageRoute( - // builder: (ctx) => PupilProfilePage( - // pupil: pupil, - // ), - // )); - }, - child: Row( - children: [ - Text( - pupil.lastName, - style: const TextStyle(fontSize: 18), + ), + ), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: InkWell( + onTap: () { + // Navigator.of(context).push(MaterialPageRoute( + // builder: (ctx) => PupilProfilePage( + // pupil: pupil, + // ), + // )); + }, + child: Row( + children: [ + Text( + pupil.lastName, + style: const TextStyle(fontSize: 18), + ), + if (pupil.family != null) ...[ + const Gap(10), + const Icon( + Icons.group, + size: 25, + color: AppColors.backgroundColor, ), - if (pupil.family != null) ...[ - const Gap(10), - const Icon(Icons.group, - size: 25, - color: AppColors.backgroundColor), - ] ], - ), + ], ), ), - ) - ], - ), - Row( - children: [ - const Text('zuletzt:'), - const Gap(10), - if (schooldayEvents.isNotEmpty) - Text( - SchoolDayEventHelper.getLastSchoolEventDate( - schooldayEvents) - .formatForUser(), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ) - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - CustomExpansionTileSwitch( - includeSwitch: true, - switchColor: AppColors.interactiveColor, - customExpansionTileController: _tileController, - expansionSwitchWidget: SchooldayEventPupilStats( - pupil: pupil, - )), - ], - ), - ], - ), + ), + ), + ], + ), + Row( + children: [ + const Text('zuletzt:'), + const Gap(10), + if (schooldayEvents.isNotEmpty) + Text( + SchoolDayEventHelper.getLastSchoolEventDate( + schooldayEvents, + ).formatDateForUser(), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + CustomExpansionTileSwitch( + includeSwitch: true, + switchColor: AppColors.interactiveColor, + customExpansionTileController: _tileController, + expansionSwitchWidget: SchooldayEventPupilStats( + pupil: pupil, + ), + ), + ], + ), + ], ), - const Gap(10), - ], + ), + const Gap(10), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 5.0, right: 5.0, bottom: 5.0), + child: CustomExpansionTileContent( + title: const Text( + 'Vorfälle', + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), + ), + tileController: _tileController, + widgetList: [PupilSchooldayEventsList(pupil: pupil)], ), - Padding( - padding: - const EdgeInsets.only(left: 5.0, right: 5.0, bottom: 5.0), - child: CustomExpansionTileContent( - title: const Text('Vorfälle', - style: - TextStyle(fontSize: 15, fontWeight: FontWeight.bold)), - tileController: _tileController, - widgetList: [PupilSchooldayEventsList(pupil: pupil)], - )), - ], - )); + ), + ], + ), + ); } } diff --git a/school_data_hub_flutter/lib/features/app_entry_point/login_page/login_page.dart b/school_data_hub_flutter/lib/features/app_entry_point/login_page/login_page.dart index 4ee51de1..803e0d5a 100644 --- a/school_data_hub_flutter/lib/features/app_entry_point/login_page/login_page.dart +++ b/school_data_hub_flutter/lib/features/app_entry_point/login_page/login_page.dart @@ -32,8 +32,9 @@ class LoginPage extends WatchingWidget { : snackbar(context, value.type, value.message), ); - final bool isAuthenticated = - watchValue((EnvManager x) => x.isAuthenticated); + final bool isAuthenticated = watchValue( + (EnvManager x) => x.isAuthenticated, + ); final locale = AppLocalizations.of(context)!; log.info('isAuthenticated: $isAuthenticated'); @@ -54,19 +55,21 @@ class LoginPage extends WatchingWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( - padding: keyboardOn - ? const EdgeInsets.only(top: 70) - : Platform.isWindows - ? const EdgeInsets.only(top: 0) - : const EdgeInsets.only(top: 0)), + padding: keyboardOn + ? const EdgeInsets.only(top: 70) + : Platform.isWindows + ? const EdgeInsets.only(top: 0) + : const EdgeInsets.only(top: 0), + ), keyboardOn ? const SizedBox.shrink() : const SizedBox( height: 250, width: 250, child: Image( - image: - AssetImage('assets/foreground_windows.png'), + image: AssetImage( + 'assets/schuldaten_hub_logo.png', + ), ), ), const Gap(20), @@ -94,25 +97,26 @@ class LoginPage extends WatchingWidget { ), ), keyboardOn - ? const SizedBox( - height: 15, - ) - : const SizedBox( - height: 15, - ), + ? const SizedBox(height: 15) + : const SizedBox(height: 15), ...[ ConstrainedBox( constraints: const BoxConstraints(maxWidth: 380), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 25.0, vertical: 8), + horizontal: 25.0, + vertical: 8, + ), child: TextField( - style: - const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), controller: controller.usernameController, decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric( - horizontal: 15, vertical: 5), + horizontal: 15, + vertical: 5, + ), filled: true, fillColor: Colors.white, labelText: locale.userName, @@ -128,7 +132,9 @@ class LoginPage extends WatchingWidget { constraints: const BoxConstraints(maxWidth: 380), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 25.0, vertical: 8), + horizontal: 25.0, + vertical: 8, + ), child: TextField( textDirection: null, controller: controller.passwordController, @@ -136,7 +142,9 @@ class LoginPage extends WatchingWidget { decoration: InputDecoration( border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( - horizontal: 15, vertical: 5), + horizontal: 15, + vertical: 5, + ), filled: true, fillColor: Colors.white, labelText: locale.password, @@ -147,9 +155,7 @@ class LoginPage extends WatchingWidget { ), ), ), - const SizedBox( - height: 40, - ), + const SizedBox(height: 40), Padding( padding: const EdgeInsets.all(10.0), child: Container( @@ -165,9 +171,10 @@ class LoginPage extends WatchingWidget { child: Text( locale.logInButtonText, style: const TextStyle( - fontSize: 17.0, - fontWeight: FontWeight.bold, - color: Colors.white), + fontSize: 17.0, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), ), ), @@ -182,18 +189,20 @@ class LoginPage extends WatchingWidget { style: AppStyles.actionButtonStyle, onPressed: () async { await confirmationDialog( - context: context, - title: locale.deleteKeyPrompt, - message: locale - .areYouSureYouWantToDeleteSchoolKey); + context: context, + title: locale.deleteKeyPrompt, + message: + locale.areYouSureYouWantToDeleteSchoolKey, + ); controller.deleteEnv(); }, child: Text( locale.deleteKeyButtonText, style: const TextStyle( - fontSize: 17.0, - fontWeight: FontWeight.bold, - color: Colors.white), + fontSize: 17.0, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), ), ), @@ -206,19 +215,22 @@ class LoginPage extends WatchingWidget { padding: const EdgeInsets.symmetric(horizontal: 12), //margin: const EdgeInsets.only(bottom: 16), child: ElevatedButton( - style: AppStyles.actionButtonStyle, - onPressed: () async { - Platform.isWindows - ? controller.importEnvFromTxt() - : controller.scanEnv(context); - }, - child: Platform.isWindows - ? const Text('SCHULSCHLÜSSEL IMPORTIEREN', - style: AppStyles.buttonTextStyle) - : Text( - locale.scanButton, - style: AppStyles.buttonTextStyle, - )), + style: AppStyles.actionButtonStyle, + onPressed: () async { + Platform.isWindows + ? controller.importEnvFromTxt() + : controller.scanEnv(context); + }, + child: Platform.isWindows + ? const Text( + 'SCHULSCHLÜSSEL IMPORTIEREN', + style: AppStyles.buttonTextStyle, + ) + : Text( + locale.scanButton, + style: AppStyles.buttonTextStyle, + ), + ), ), ), ], diff --git a/school_data_hub_flutter/lib/features/app_main_navigation/widgets/pupil_lists_buttons.dart b/school_data_hub_flutter/lib/features/app_main_navigation/widgets/pupil_lists_buttons.dart index 9ddf0e30..36e3119e 100644 --- a/school_data_hub_flutter/lib/features/app_main_navigation/widgets/pupil_lists_buttons.dart +++ b/school_data_hub_flutter/lib/features/app_main_navigation/widgets/pupil_lists_buttons.dart @@ -10,6 +10,8 @@ import 'package:school_data_hub_flutter/features/learning_support/presentation/l import 'package:school_data_hub_flutter/features/matrix/users/presentation/matrix_users_list_page/matrix_users_list_page.dart'; import 'package:school_data_hub_flutter/features/ogs/ogs_list_page.dart'; import 'package:school_data_hub_flutter/features/pupil/presentation/_credit/credit_list_page/credit_list_page.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/family_language_lessons_page/family_language_lessons_list_page.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/religion_page/religion_list_page.dart'; import 'package:school_data_hub_flutter/features/pupil/presentation/special_info_page/special_info_list_page.dart'; import 'package:school_data_hub_flutter/l10n/app_localizations.dart'; import 'package:watch_it/watch_it.dart'; @@ -71,7 +73,7 @@ class PupilListButtons extends WatchingWidget { ), buttonText: locale.pupilCredit, ), - if (isReady && isTester) + if (isReady) MainMenuButton( destinationPage: const LearningPupilListPage(), buttonIcon: const Icon( @@ -99,19 +101,53 @@ class PupilListButtons extends WatchingWidget { ), buttonText: locale.specialInfo, ), - if (isReady && isTester) - MainMenuButton( - destinationPage: const OgsListPage(), - buttonIcon: Text( - locale.allDayCare, - style: const TextStyle( - fontSize: 35, + const MainMenuButton( + destinationPage: const ReligionListPage(), + buttonIcon: const Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Icon( + Icons.mosque_rounded, + size: 50, + color: AppColors.gridViewColor, + ), + Icon( + Icons.church_rounded, + size: 50, + color: AppColors.gridViewColor, + ), + ], + ), + buttonText: 'Reli-Unterricht', + ), + const MainMenuButton( + destinationPage: const FamilyLanguageLessonsListPage(), + buttonIcon: const Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Icon( + Icons.translate_rounded, + size: 50, color: AppColors.gridViewColor, - fontWeight: FontWeight.bold, ), + ], + ), + buttonText: 'HSU', + ), + MainMenuButton( + destinationPage: const OgsListPage(), + buttonIcon: Text( + locale.allDayCare, + style: const TextStyle( + fontSize: 35, + color: AppColors.gridViewColor, + fontWeight: FontWeight.bold, ), - buttonText: locale.allDayCare, ), + buttonText: locale.allDayCare, + ), if (matrixSessionConfigured) MainMenuButton( destinationPage: const MatrixUsersListPage(), diff --git a/school_data_hub_flutter/lib/features/app_settings/settings_page/widgets/settings_session_section.dart b/school_data_hub_flutter/lib/features/app_settings/settings_page/widgets/settings_session_section.dart index a94cb453..073d3f16 100644 --- a/school_data_hub_flutter/lib/features/app_settings/settings_page/widgets/settings_session_section.dart +++ b/school_data_hub_flutter/lib/features/app_settings/settings_page/widgets/settings_session_section.dart @@ -3,7 +3,7 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_settings_ui/flutter_settings_ui.dart'; import 'package:gap/gap.dart'; import 'package:logging/logging.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; import 'package:school_data_hub_flutter/core/di/dependency_injection.dart'; @@ -59,7 +59,8 @@ class SettingsSessionSection extends AbstractSettingsSection with WatchItMixin { leading: const Icon(Icons.perm_identity_rounded), title: const Text('Personenbezogene Daten vom:'), value: Text( - di().activeEnv?.lastIdentitiesUpdate?.formatForUser() ?? + di().activeEnv?.lastIdentitiesUpdate + ?.formatDateForUser() ?? 'unbekannt', ), trailing: null, diff --git a/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/book_card.dart b/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/book_card.dart index 68d1e419..5491e4e8 100644 --- a/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/book_card.dart +++ b/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/book_card.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/isbn_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; @@ -86,24 +86,23 @@ class BookCard extends WatchingWidget { SingleChildScrollView( scrollDirection: Axis.horizontal, child: InkWell( - onLongPress: - (di().isAdmin) - ? () { - // Navigator.of(context).push(MaterialPageRoute( - // builder: (ctx) => NewBook( - // isEdit: true, - // bookAuthor: books.first.author, - // bookId: book.bookId, - // isbn: book.isbn, - // bookReadingLevel: book.readingLevel, - // bookTitle: book.title, - // bookDescription: book.description, - // bookImageId: book.imageId, - // location: book.location, - // ), - // )); - } - : () {}, + onLongPress: (di().isAdmin) + ? () { + // Navigator.of(context).push(MaterialPageRoute( + // builder: (ctx) => NewBook( + // isEdit: true, + // bookAuthor: books.first.author, + // bookId: book.bookId, + // isbn: book.isbn, + // bookReadingLevel: book.readingLevel, + // bookTitle: book.title, + // bookDescription: book.description, + // bookImageId: book.imageId, + // location: book.location, + // ), + // )); + } + : () {}, child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -255,10 +254,9 @@ class BookCard extends WatchingWidget { ], ), Column( - children: - bookProxies.map((book) { - return LibraryBookCard(libraryBookProxy: book); - }).toList(), + children: bookProxies.map((book) { + return LibraryBookCard(libraryBookProxy: book); + }).toList(), ), const Gap(10), ], diff --git a/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/book_pupil_card.dart b/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/book_pupil_card.dart index 81f4f4e6..646ce5e4 100644 --- a/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/book_pupil_card.dart +++ b/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/book_pupil_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/long_textfield_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/growth_dropdown.dart'; @@ -70,9 +70,8 @@ class BookLendingPupilCard extends WatchingWidget { ); Navigator.of(context).push( MaterialPageRoute( - builder: - (ctx) => - PupilProfilePage(pupil: pupil), + builder: (ctx) => + PupilProfilePage(pupil: pupil), ), ); }, @@ -122,7 +121,7 @@ class BookLendingPupilCard extends WatchingWidget { ), const Gap(2), Text( - watchedPupilBook.lentAt.formatForUser(), + watchedPupilBook.lentAt.formatDateForUser(), style: const TextStyle(fontWeight: FontWeight.bold), ), ], @@ -143,7 +142,7 @@ class BookLendingPupilCard extends WatchingWidget { ), const Gap(2), Text( - watchedPupilBook.returnedAt!.formatForUser(), + watchedPupilBook.returnedAt!.formatDateForUser(), style: const TextStyle( fontWeight: FontWeight.bold, ), diff --git a/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/pupil_book_card.dart b/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/pupil_book_card.dart index 30e834c9..e6df4f53 100644 --- a/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/pupil_book_card.dart +++ b/school_data_hub_flutter/lib/features/books/presentation/book_list_page/widgets/pupil_book_card.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/information_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/upload_image.dart'; @@ -26,8 +26,9 @@ class PupilBookCard extends WatchingWidget { @override Widget build(BuildContext context) { - final LibraryBookProxy bookProxy = - di().getLibraryBookById(pupilBook.libraryBookId)!; + final LibraryBookProxy bookProxy = di().getLibraryBookById( + pupilBook.libraryBookId, + )!; return ClipRRect( borderRadius: BorderRadius.circular(20), child: Card( @@ -185,7 +186,7 @@ class PupilBookCard extends WatchingWidget { const Text('am'), const Gap(5), Text( - pupilBook.lentAt.formatForUser(), + pupilBook.lentAt.formatDateForUser(), style: const TextStyle( fontWeight: FontWeight.bold, ), diff --git a/school_data_hub_flutter/lib/features/books/presentation/book_search_page/book_search_result_card.dart b/school_data_hub_flutter/lib/features/books/presentation/book_search_page/book_search_result_card.dart index 8fd063bc..d9c05c81 100644 --- a/school_data_hub_flutter/lib/features/books/presentation/book_search_page/book_search_result_card.dart +++ b/school_data_hub_flutter/lib/features/books/presentation/book_search_page/book_search_result_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/isbn_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/long_textfield_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/unencrypted_image_in_card.dart'; @@ -46,8 +46,8 @@ class SearchResultBookCard extends WatchingWidget { Navigator.push( context, MaterialPageRoute( - builder: - (context) => EditBook(libraryBook: bookProxy), + builder: (context) => + EditBook(libraryBook: bookProxy), ), ); }, @@ -206,25 +206,20 @@ class SearchResultBookCard extends WatchingWidget { ], ), Column( - children: - books - .fold>([], ( - uniqueBooks, - book, - ) { - // Only add if libraryId is not already in the list - if (!uniqueBooks.any( - (existing) => - existing.libraryId == book.libraryId, - )) { - uniqueBooks.add(book); - } - return uniqueBooks; - }) - .map((book) { - return LibraryBookCard(libraryBookProxy: book); - }) - .toList(), + children: books + .fold>([], (uniqueBooks, book) { + // Only add if libraryId is not already in the list + if (!uniqueBooks.any( + (existing) => existing.libraryId == book.libraryId, + )) { + uniqueBooks.add(book); + } + return uniqueBooks; + }) + .map((book) { + return LibraryBookCard(libraryBookProxy: book); + }) + .toList(), ), const Gap(10), ], diff --git a/school_data_hub_flutter/lib/features/books/presentation/widgets/pupil_book_card.dart b/school_data_hub_flutter/lib/features/books/presentation/widgets/pupil_book_card.dart index cf3543ad..02b03797 100644 --- a/school_data_hub_flutter/lib/features/books/presentation/widgets/pupil_book_card.dart +++ b/school_data_hub_flutter/lib/features/books/presentation/widgets/pupil_book_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; @@ -26,8 +26,9 @@ class PupilBookLendingCard extends StatelessWidget { @override Widget build(BuildContext context) { - final LibraryBookProxy book = - di().getLibraryBookById(pupilBookLending.libraryBookId)!; + final LibraryBookProxy book = di().getLibraryBookById( + pupilBookLending.libraryBookId, + )!; void updatepupilBookRating(int rating) { di().updatePupilBookLending( pupilBookLending: pupilBookLending, @@ -129,7 +130,7 @@ class PupilBookLendingCard extends StatelessWidget { Text( pupilBookLending.lentAt .toLocal() - .formatForUser(), + .formatDateForUser(), style: const TextStyle( fontWeight: FontWeight.bold, ), @@ -153,7 +154,7 @@ class PupilBookLendingCard extends StatelessWidget { const Gap(2), Text( pupilBookLending.returnedAt! - .formatForUser(), + .formatDateForUser(), style: const TextStyle( fontWeight: FontWeight.bold, ), diff --git a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/learning_list_card/learning_list_card.dart b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/learning_list_card/learning_list_card.dart index 8658d1ed..dcd42851 100644 --- a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/learning_list_card/learning_list_card.dart +++ b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/learning_list_card/learning_list_card.dart @@ -42,8 +42,9 @@ class LearningListCard extends WatchingWidget { // Calculate book lending statistics for this pupil final pupilBookLendings = pupil.pupilBookLendings ?? []; final totalLendings = pupilBookLendings.length; - final notReturnedLendings = - pupilBookLendings.where((lending) => lending.returnedAt == null).length; + final notReturnedLendings = pupilBookLendings + .where((lending) => lending.returnedAt == null) + .length; return Card( color: Colors.white, @@ -82,8 +83,8 @@ class LearningListCard extends WatchingWidget { ); Navigator.of(context).push( MaterialPageRoute( - builder: - (ctx) => PupilProfilePage(pupil: pupil), + builder: (ctx) => + PupilProfilePage(pupil: pupil), ), ); }, @@ -119,6 +120,7 @@ class LearningListCard extends WatchingWidget { ), ], ), + const Gap(5), if (selectedContent == SelectedContent.competenceStatuses) ...[ diff --git a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/competence_check_card.dart b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/competence_check_card.dart index 5f3417d3..1b281a38 100644 --- a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/competence_check_card.dart +++ b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/competence_check_card.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; @@ -68,73 +68,75 @@ class CompetenceCheckCard extends StatelessWidget { children: [ isAuthorized ? InkWell( - onTap: () async { - DateTime? date = await selectSchooldayDate( - context, - di().thisDate.value, - ); - if (date == null) return; + onTap: () async { + DateTime? date = await selectSchooldayDate( + context, + di().thisDate.value, + ); + if (date == null) return; - await di().updateCompetenceCheck( - competenceCheckId: competenceCheck.checkId, - createdAt: (value: date), - ); - }, - child: Text( - competenceCheck.createdAt.formatForUser(), + await di() + .updateCompetenceCheck( + competenceCheckId: competenceCheck.checkId, + createdAt: (value: date), + ); + }, + child: Text( + competenceCheck.createdAt.formatDateForUser(), + style: const TextStyle( + color: AppColors.interactiveColor, + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ) + : Text( + competenceCheck.createdAt.formatDateForUser(), style: const TextStyle( - color: AppColors.interactiveColor, + color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20, ), ), - ) - : Text( - competenceCheck.createdAt.formatForUser(), - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), const Spacer(), const Text('Erstellt von:', style: TextStyle(fontSize: 16)), const Gap(5), // only admin can change the admonishing user isAuthorized ? InkWell( - onTap: () async { - final String? user = await shortTextfieldDialog( - context: context, - title: 'Erstellt von:', - labelText: 'Kürzel eingeben', - hintText: 'Kürzel eingeben', - obscureText: false, - ); - if (user != null) { - await di() - .updateCompetenceCheck( - competenceCheckId: competenceCheck.checkId, - createdBy: (value: user), - ); - } - }, - child: Text( + onTap: () async { + final String? user = await shortTextfieldDialog( + context: context, + title: 'Erstellt von:', + labelText: 'Kürzel eingeben', + hintText: 'Kürzel eingeben', + obscureText: false, + ); + if (user != null) { + await di() + .updateCompetenceCheck( + competenceCheckId: + competenceCheck.checkId, + createdBy: (value: user), + ); + } + }, + child: Text( + competenceCheck.createdBy, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: AppColors.backgroundColor, + ), + ), + ) + : Text( competenceCheck.createdBy, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, - color: AppColors.backgroundColor, ), ), - ) - : Text( - competenceCheck.createdBy, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), const Gap(5), ], ), @@ -145,59 +147,103 @@ class CompetenceCheckCard extends StatelessWidget { const Gap(5), isAuthorized ? GrowthDropdown( - dropdownValue: competenceCheck.score, - onChangedFunction: (int value) async { - if (value == competenceCheck.score) { - return; - } - await di().updateCompetenceCheck( - competenceCheckId: competenceCheck.checkId, - score: (value: value), - ); - }, - ) + dropdownValue: competenceCheck.score, + onChangedFunction: (int value) async { + if (value == competenceCheck.score) { + return; + } + await di() + .updateCompetenceCheck( + competenceCheckId: competenceCheck.checkId, + score: (value: value), + ); + }, + ) : Padding( - padding: const EdgeInsets.all(5.0), - child: CompetenceHelper.getCompetenceCheckSymbol( - status: competenceCheck.score, - size: 60, + padding: const EdgeInsets.all(5.0), + child: CompetenceHelper.getCompetenceCheckSymbol( + status: competenceCheck.score, + size: 60, + ), ), - ), const Gap(10), // Value Factor Display isAuthorized ? InkWell( - onTap: () async { - final String? valueFactorText = - await shortTextfieldDialog( - context: context, - title: 'Wertfaktor', - labelText: 'Wertfaktor eingeben', - hintText: 'z.B. 1.5', - textinField: competenceCheck.valueFactor - .toStringAsFixed(1), - obscureText: false, - ); - if (valueFactorText != null) { - final double? valueFactor = double.tryParse( - valueFactorText, - ); - if (valueFactor != null && valueFactor > 0) { - await di() - .updateCompetenceCheck( - competenceCheckId: - competenceCheck.checkId, - valueFactor: (value: valueFactor), - ); - } else { - di().showSnackBar( - NotificationType.error, - 'Ungültiger Wertfaktor. Bitte geben Sie eine positive Zahl ein.', + onTap: () async { + final String? valueFactorText = + await shortTextfieldDialog( + context: context, + title: 'Wertfaktor', + labelText: 'Wertfaktor eingeben', + hintText: 'z.B. 1.5', + textinField: competenceCheck.valueFactor + .toStringAsFixed(1), + obscureText: false, + ); + if (valueFactorText != null) { + final double? valueFactor = double.tryParse( + valueFactorText, ); + if (valueFactor != null && valueFactor > 0) { + await di() + .updateCompetenceCheck( + competenceCheckId: + competenceCheck.checkId, + valueFactor: (value: valueFactor), + ); + } else { + di().showSnackBar( + NotificationType.error, + 'Ungültiger Wertfaktor. Bitte geben Sie eine positive Zahl ein.', + ); + } } - } - }, - child: Container( + }, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColors.backgroundColor.withValues( + alpha: 0.1, + ), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppColors.backgroundColor.withValues( + alpha: 0.3, + ), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'x', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.interactiveColor, + ), + ), + const Gap(4), + Text( + competenceCheck.valueFactor.toStringAsFixed( + 1, + ), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.interactiveColor, + ), + ), + ], + ), + ), + ) + : Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, @@ -222,7 +268,7 @@ class CompetenceCheckCard extends StatelessWidget { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: AppColors.interactiveColor, + color: AppColors.backgroundColor, ), ), const Gap(4), @@ -233,53 +279,12 @@ class CompetenceCheckCard extends StatelessWidget { style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: AppColors.interactiveColor, + color: AppColors.backgroundColor, ), ), ], ), ), - ) - : Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: AppColors.backgroundColor.withValues( - alpha: 0.1, - ), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: AppColors.backgroundColor.withValues( - alpha: 0.3, - ), - width: 1, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'x', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: AppColors.backgroundColor, - ), - ), - const Gap(4), - Text( - competenceCheck.valueFactor.toStringAsFixed(1), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: AppColors.backgroundColor, - ), - ), - ], - ), - ), const Spacer(), //- Take picture button only visible if there are less than 4 pictures if (competenceCheck.documents == null || diff --git a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_goals/competence_goal_card.dart b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_goals/competence_goal_card.dart index 93c3d890..89778ced 100644 --- a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_goals/competence_goal_card.dart +++ b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_goals/competence_goal_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/features/learning/domain/competence_helper.dart'; import 'package:school_data_hub_flutter/features/learning/domain/competence_manager.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; @@ -10,8 +10,11 @@ import 'package:watch_it/watch_it.dart'; class CompetenceGoalCard extends StatelessWidget { final CompetenceGoal pupilGoal; final PupilProxy pupil; - const CompetenceGoalCard( - {required this.pupilGoal, required this.pupil, super.key}); + const CompetenceGoalCard({ + required this.pupilGoal, + required this.pupil, + super.key, + }); @override Widget build(BuildContext context) { @@ -26,7 +29,8 @@ class CompetenceGoalCard extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(5.0), color: CompetenceHelper.getCompetenceColor( - pupilGoal.competenceId), + pupilGoal.competenceId, + ), ), child: Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), @@ -38,9 +42,10 @@ class CompetenceGoalCard extends StatelessWidget { .findRootCompetenceById(pupilGoal.competenceId) .name, style: const TextStyle( - fontSize: 19, - fontWeight: FontWeight.bold, - color: Colors.white), + fontSize: 19, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), ], ), @@ -50,7 +55,9 @@ class CompetenceGoalCard extends StatelessWidget { Row( children: [ CompetenceHelper.getCompetenceCheckSymbol( - status: pupilGoal.score ?? 0, size: 50), + status: pupilGoal.score ?? 0, + size: 50, + ), const Gap(10), Flexible( child: Text( @@ -58,7 +65,9 @@ class CompetenceGoalCard extends StatelessWidget { .findCompetenceById(pupilGoal.competenceId) .name, style: const TextStyle( - fontSize: 17, fontWeight: FontWeight.bold), + fontSize: 17, + fontWeight: FontWeight.bold, + ), ), ), ], @@ -72,9 +81,11 @@ class CompetenceGoalCard extends StatelessWidget { child: Text( pupilGoal.description, style: const TextStyle( - fontSize: 16, fontWeight: FontWeight.bold), + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), - ) + ), ], ), const Gap(5), @@ -101,11 +112,11 @@ class CompetenceGoalCard extends StatelessWidget { const Text('am'), const Gap(10), Text( - pupilGoal.createdAt.formatForUser(), + pupilGoal.createdAt.formatDateForUser(), style: const TextStyle(fontWeight: FontWeight.bold), ), ], - ) + ), ], ), ), diff --git a/school_data_hub_flutter/lib/features/learning/presentation/widgets/competence_check_comment.dart b/school_data_hub_flutter/lib/features/learning/presentation/widgets/competence_check_comment.dart index e1ff80d3..7607ac05 100644 --- a/school_data_hub_flutter/lib/features/learning/presentation/widgets/competence_check_comment.dart +++ b/school_data_hub_flutter/lib/features/learning/presentation/widgets/competence_check_comment.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/features/learning/domain/competence_helper.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; @@ -9,7 +9,9 @@ Widget getCompetenceCheckComment(PupilProxy pupil, int competenceId) { if (pupil.competenceChecks!.isNotEmpty) { final CompetenceCheck? competenceCheck = CompetenceHelper.getLastCompetenceCheckOfCompetence( - pupil, competenceId); + pupil, + competenceId, + ); if (competenceCheck != null) { return Padding( padding: const EdgeInsets.only(left: 35), @@ -22,10 +24,7 @@ Widget getCompetenceCheckComment(PupilProxy pupil, int competenceId) { competenceCheck.comment ?? 'Kein Kommentar vorhanden', maxLines: 2, textAlign: TextAlign.start, - style: const TextStyle( - color: Colors.white, - fontSize: 15, - ), + style: const TextStyle(color: Colors.white, fontSize: 15), ), ], ), @@ -35,36 +34,32 @@ Widget getCompetenceCheckComment(PupilProxy pupil, int competenceId) { children: [ const Text( 'eingetragen von ', - style: TextStyle( - color: Colors.white, - fontSize: 12, - ), + style: TextStyle(color: Colors.white, fontSize: 12), ), Text( competenceCheck.createdBy, style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold), + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), const Gap(5), const Text( 'am', - style: TextStyle( - color: Colors.white, - fontSize: 12, - ), + style: TextStyle(color: Colors.white, fontSize: 12), ), const Gap(5), Text( - competenceCheck.createdAt.toLocal().formatForUser(), + competenceCheck.createdAt.toLocal().formatDateForUser(), style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold), + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), ], - ) + ), ], ), ); diff --git a/school_data_hub_flutter/lib/features/learning/presentation/widgets/pupil_learning_content_expansion_tile_nav_bar.dart b/school_data_hub_flutter/lib/features/learning/presentation/widgets/pupil_learning_content_expansion_tile_nav_bar.dart index df9fd589..2deee560 100644 --- a/school_data_hub_flutter/lib/features/learning/presentation/widgets/pupil_learning_content_expansion_tile_nav_bar.dart +++ b/school_data_hub_flutter/lib/features/learning/presentation/widgets/pupil_learning_content_expansion_tile_nav_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; import 'package:school_data_hub_flutter/features/learning/presentation/pupil_competence_list_page/widgets/pupil_learning_content/pupil_learning_content_books.dart'; import 'package:school_data_hub_flutter/features/learning/presentation/pupil_competence_list_page/widgets/pupil_learning_content/pupil_learning_content_competence_goals.dart'; import 'package:school_data_hub_flutter/features/learning/presentation/pupil_competence_list_page/widgets/pupil_learning_content/pupil_learning_content_competence_statuses.dart'; @@ -41,8 +42,10 @@ class SelectedLearningContentNotifier extends ChangeNotifier { class PupilLearningContentExpansionTileNavBar extends WatchingWidget { final PupilProxy pupil; - const PupilLearningContentExpansionTileNavBar( - {required this.pupil, super.key}); + const PupilLearningContentExpansionTileNavBar({ + required this.pupil, + super.key, + }); @override Widget build(BuildContext context) { @@ -53,16 +56,17 @@ class PupilLearningContentExpansionTileNavBar extends WatchingWidget { children: [ const PupilLearningContentNavBar(), Padding( - padding: const EdgeInsets.only(top: 5), - child: (selectedContent == SelectedContent.competenceStatuses) - ? PupilLearningContentCompetenceStatuses(pupil: pupil) - : (selectedContent == SelectedContent.competenceGoals) - ? PupilLearningContentCompetenceGoals(pupil: pupil) - : (selectedContent == SelectedContent.workbooks) - ? PupilLearningContentWorkbooks(pupil: pupil) - : - // (selectedContent == SelectedContent.books): - PupilLearningContentBooks(pupil: pupil)) + padding: const EdgeInsets.only(top: 5), + child: (selectedContent == SelectedContent.competenceStatuses) + ? PupilLearningContentCompetenceStatuses(pupil: pupil) + : (selectedContent == SelectedContent.competenceGoals) + ? PupilLearningContentCompetenceGoals(pupil: pupil) + : (selectedContent == SelectedContent.workbooks) + ? PupilLearningContentWorkbooks(pupil: pupil) + : + // (selectedContent == SelectedContent.books): + PupilLearningContentBooks(pupil: pupil), + ), ], ); } @@ -83,79 +87,146 @@ class PupilLearningContentNavBar extends WatchingWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - IconButton( - isSelected: selectedContent == SelectedContent.competenceStatuses, - icon: const Icon( - Icons.lightbulb, - color: AppColors.interactiveColor, - ), - selectedIcon: const Icon( - Icons.lightbulb, - color: AppColors.accentColor, - ), - onPressed: () { - if (selectedContent != SelectedContent.competenceStatuses) { - selectedContentNotifier - .select(SelectedContent.competenceStatuses); - - return; - } - }, - ), - IconButton( - isSelected: selectedContent == SelectedContent.competenceGoals, - icon: const Icon( - Icons.emoji_nature_rounded, - color: AppColors.interactiveColor, - ), - selectedIcon: const Icon( - Icons.emoji_nature_rounded, - color: AppColors.accentColor, + if (di().isTester) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + isSelected: + selectedContent == SelectedContent.competenceStatuses, + icon: const Icon( + Icons.lightbulb, + color: AppColors.interactiveColor, + ), + selectedIcon: const Icon( + Icons.lightbulb, + color: AppColors.accentColor, + ), + onPressed: () { + if (selectedContent != + SelectedContent.competenceStatuses) { + selectedContentNotifier.select( + SelectedContent.competenceStatuses, + ); + + return; + } + }, + ), + Text( + 'Lernspuren', + style: TextStyle( + color: + selectedContent == SelectedContent.competenceStatuses + ? AppColors.accentColor + : AppColors.interactiveColor, + fontSize: 12, + ), + ), + ], ), - onPressed: () { - if (selectedContent != SelectedContent.competenceGoals) { - selectedContentNotifier - .select(SelectedContent.competenceGoals); - - return; - } - }, - ), - IconButton( - isSelected: selectedContent == SelectedContent.workbooks, - icon: const Icon( - Icons.note_alt, - color: AppColors.interactiveColor, + if (di().isTester) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + isSelected: + selectedContent == SelectedContent.competenceGoals, + icon: const Icon( + Icons.emoji_nature_rounded, + color: AppColors.interactiveColor, + ), + selectedIcon: const Icon( + Icons.emoji_nature_rounded, + color: AppColors.accentColor, + ), + onPressed: () { + if (selectedContent != SelectedContent.competenceGoals) { + selectedContentNotifier.select( + SelectedContent.competenceGoals, + ); + + return; + } + }, + ), + Text( + 'Ziele', + style: TextStyle( + color: selectedContent == SelectedContent.competenceGoals + ? AppColors.accentColor + : AppColors.interactiveColor, + fontSize: 12, + ), + ), + ], ), - selectedIcon: const Icon( - Icons.note_alt, - color: AppColors.accentColor, + if (di().isTester) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + isSelected: selectedContent == SelectedContent.workbooks, + icon: const Icon( + Icons.note_alt, + color: AppColors.interactiveColor, + ), + selectedIcon: const Icon( + Icons.note_alt, + color: AppColors.accentColor, + ), + onPressed: () { + if (selectedContent != SelectedContent.workbooks) { + selectedContentNotifier.select( + SelectedContent.workbooks, + ); + + return; + } + }, + ), + Text( + 'Arbeitshefte', + style: TextStyle( + color: selectedContent == SelectedContent.workbooks + ? AppColors.accentColor + : AppColors.interactiveColor, + fontSize: 12, + ), + ), + ], ), - onPressed: () { - if (selectedContent != SelectedContent.workbooks) { - selectedContentNotifier.select(SelectedContent.workbooks); - - return; - } - }, - ), - IconButton( - isSelected: selectedContent == SelectedContent.books, - icon: const Icon( - Icons.book, - color: AppColors.interactiveColor, - ), - selectedIcon: const Icon( - Icons.book, - color: AppColors.accentColor, - ), - onPressed: () { - if (selectedContent != SelectedContent.books) { - selectedContentNotifier.select(SelectedContent.books); - - return; - } - }, + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + isSelected: selectedContent == SelectedContent.books, + icon: const Icon( + Icons.book, + color: AppColors.interactiveColor, + ), + selectedIcon: const Icon( + Icons.book, + color: AppColors.accentColor, + ), + onPressed: () { + if (selectedContent != SelectedContent.books) { + selectedContentNotifier.select(SelectedContent.books); + + return; + } + }, + ), + Text( + 'Bücher', + style: TextStyle( + color: selectedContent == SelectedContent.books + ? AppColors.accentColor + : AppColors.interactiveColor, + fontSize: 12, + ), + ), + ], ), ], ), diff --git a/school_data_hub_flutter/lib/features/learning_support/presentation/learning_support_list_page/widgets/learning_support_list_card.dart b/school_data_hub_flutter/lib/features/learning_support/presentation/learning_support_list_page/widgets/learning_support_list_card.dart index 8b037663..4265a5ee 100644 --- a/school_data_hub_flutter/lib/features/learning_support/presentation/learning_support_list_page/widgets/learning_support_list_card.dart +++ b/school_data_hub_flutter/lib/features/learning_support/presentation/learning_support_list_page/widgets/learning_support_list_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile_content.dart'; @@ -70,8 +71,8 @@ class _LearningSupportCardState extends State { ); Navigator.of(context).push( MaterialPageRoute( - builder: - (ctx) => PupilProfilePage(pupil: pupil), + builder: (ctx) => + PupilProfilePage(pupil: pupil), ), ); }, @@ -135,6 +136,25 @@ class _LearningSupportCardState extends State { ), ], ), + if (pupil.migrationSupportEnds != null) + Wrap( + children: [ + const Text('Ende der Erstförderung: '), + Text( + pupil.migrationSupportEnds!.formatDateForUser(), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: + pupil.migrationSupportEnds!.isAfter( + DateTime(2026, 08, 01), + ) + ? Colors.red + : Colors.black, + ), + ), + ], + ), const Gap(15), if (pupil.supportCategoryStatuses != null) if (pupil.supportCategoryStatuses!.isNotEmpty) @@ -168,8 +188,8 @@ class _LearningSupportCardState extends State { child: Text( pupil.latestSupportLevel != null ? pupil.latestSupportLevel!.level == 4 - ? '🌈' - : pupil.latestSupportLevel!.level.toString() + ? '🌈' + : pupil.latestSupportLevel!.level.toString() : '0', style: const TextStyle( fontSize: 23, @@ -181,8 +201,8 @@ class _LearningSupportCardState extends State { Text( pupil.specialNeeds != null ? pupil.specialNeeds!.length == 4 - ? '${pupil.specialNeeds!.substring(0, 2)} ${pupil.specialNeeds!.substring(2, 4)}' - : pupil.specialNeeds!.substring(0, 2) + ? '${pupil.specialNeeds!.substring(0, 2)} ${pupil.specialNeeds!.substring(2, 4)}' + : pupil.specialNeeds!.substring(0, 2) : '', style: const TextStyle( fontWeight: FontWeight.bold, diff --git a/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/dialogs/support_level_dialog.dart b/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/dialogs/support_level_dialog.dart index bd46068e..c823f47b 100644 --- a/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/dialogs/support_level_dialog.dart +++ b/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/dialogs/support_level_dialog.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; @@ -135,7 +135,7 @@ Future supportLevelDialog( } }, child: Text( - selectedDate.formatForUser(), + selectedDate.formatDateForUser(), style: const TextStyle( color: AppColors.backgroundColor, fontWeight: FontWeight.bold, diff --git a/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_catagory_status/support_category_statuses_list.dart b/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_catagory_status/support_category_statuses_list.dart index 91d90ee8..5fc8854b 100644 --- a/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_catagory_status/support_category_statuses_list.dart +++ b/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_catagory_status/support_category_statuses_list.dart @@ -2,7 +2,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/features/learning_support/presentation/widgets/support_catagory_status/widgets/support_category_status_card.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; @@ -13,29 +13,38 @@ List pupilCategoryStatusesList(PupilProxy pupil, BuildContext context) { Map> statusesWithDuplicateGoalCategory = {}; for (SupportCategoryStatus status in pupil.supportCategoryStatuses!) { - if (pupil.supportCategoryStatuses!.any((element) => - element.supportCategoryId == status.supportCategoryId && - pupil.supportCategoryStatuses!.indexOf(element) != - pupil.supportCategoryStatuses!.indexOf(status))) { + if (pupil.supportCategoryStatuses!.any( + (element) => + element.supportCategoryId == status.supportCategoryId && + pupil.supportCategoryStatuses!.indexOf(element) != + pupil.supportCategoryStatuses!.indexOf(status), + )) { //- This one is duplicate. Adding a key / widget in the map - if (!statusesWithDuplicateGoalCategory - .containsKey(status.supportCategoryId)) { + if (!statusesWithDuplicateGoalCategory.containsKey( + status.supportCategoryId, + )) { statusesWithDuplicateGoalCategory[(status.supportCategoryId)] = List.empty(growable: true); - statusesWithDuplicateGoalCategory[(status.supportCategoryId)]! - .add(status); + statusesWithDuplicateGoalCategory[(status.supportCategoryId)]!.add( + status, + ); } else { - statusesWithDuplicateGoalCategory[(status.supportCategoryId)]! - .add(status); + statusesWithDuplicateGoalCategory[(status.supportCategoryId)]!.add( + status, + ); } - log('Adding status vom ${status.createdAt.formatForUser()} erstellt von ${status.createdBy}'); + log( + 'Adding status vom ${status.createdAt.formatDateForUser()} erstellt von ${status.createdBy}', + ); } else { statusesWidgetList.add( Padding( padding: const EdgeInsets.only(bottom: 8.0), child: SupportCategoryStatusCard( - pupil: pupil, statusesWithSameGoalCategory: [status]), + pupil: pupil, + statusesWithSameGoalCategory: [status], + ), ), ); } diff --git a/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_catagory_status/widgets/support_category_status_entry/support_category_status_entry.dart b/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_catagory_status/widgets/support_category_status_entry/support_category_status_entry.dart index 8380c9ed..4d525c08 100644 --- a/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_catagory_status/widgets/support_category_status_entry/support_category_status_entry.dart +++ b/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_catagory_status/widgets/support_category_status_entry/support_category_status_entry.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/information_dialog.dart'; @@ -72,69 +72,69 @@ class SupportCategoryStatusEntry extends StatelessWidget { children: [ authorizedToChangeStatus ? InkWell( - onTap: () async { - final DateTime? correctedCreatedAt = - await showDatePicker( - context: context, - initialDate: status.createdAt, - firstDate: DateTime(2000), - lastDate: DateTime.now().toUtc(), - ); - if (correctedCreatedAt != null) { - // TODO: uncomment when ready - // _learningSupportManager - // .updateSupportCategoryStatusProperty( - // pupil: pupil, - // statusId: status.statusId, - // createdAt: correctedCreatedAt.formatForJson(), - // ); - } - }, - child: Text( - status.createdAt.formatForUser(), + onTap: () async { + final DateTime? correctedCreatedAt = + await showDatePicker( + context: context, + initialDate: status.createdAt, + firstDate: DateTime(2000), + lastDate: DateTime.now().toUtc(), + ); + if (correctedCreatedAt != null) { + // TODO: uncomment when ready + // _learningSupportManager + // .updateSupportCategoryStatusProperty( + // pupil: pupil, + // statusId: status.statusId, + // createdAt: correctedCreatedAt.formatForJson(), + // ); + } + }, + child: Text( + status.createdAt.formatDateForUser(), + style: const TextStyle( + color: AppColors.interactiveColor, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ) + : Text( + status.createdAt.formatDateForUser(), style: const TextStyle( - color: AppColors.interactiveColor, fontWeight: FontWeight.bold, fontSize: 18, ), ), - ) - : Text( - status.createdAt.formatForUser(), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), const Gap(5), authorizedToChangeStatus ? InkWell( - onTap: () async { - final String? correctedComment = - await longTextFieldDialog( - title: 'Status korrigieren', - labelText: 'Status', - initialValue: status.comment, - parentContext: context, - ); - if (correctedComment != null) { - // TODO: uncomment when ready - // _learningSupportManager - // .updateSupportCategoryStatusProperty( - // pupil: pupil, - // statusId: status.statusId, - // comment: correctedComment, - // ); - } - }, - child: Text( - status.comment, - style: const TextStyle( - color: AppColors.interactiveColor, - fontWeight: FontWeight.bold, + onTap: () async { + final String? correctedComment = + await longTextFieldDialog( + title: 'Status korrigieren', + labelText: 'Status', + initialValue: status.comment, + parentContext: context, + ); + if (correctedComment != null) { + // TODO: uncomment when ready + // _learningSupportManager + // .updateSupportCategoryStatusProperty( + // pupil: pupil, + // statusId: status.statusId, + // comment: correctedComment, + // ); + } + }, + child: Text( + status.comment, + style: const TextStyle( + color: AppColors.interactiveColor, + fontWeight: FontWeight.bold, + ), ), - ), - ) + ) : Text(status.comment), const Gap(5), Wrap( @@ -143,37 +143,39 @@ class SupportCategoryStatusEntry extends StatelessWidget { const Gap(5), authorizedToChangeStatus ? InkWell( - onTap: () async { - final String? correctedCreatedBy = - await shortTextfieldDialog( - title: 'Ersteller ändern', - obscureText: false, - hintText: 'Kürzel eintragen', - labelText: status.createdBy, - context: context, - ); - if (correctedCreatedBy != null) { - // TODO: uncomment when ready - // _learningSupportManager - // .updateSupportCategoryStatusProperty( - // pupil: pupil, - // statusId: status.statusId, - // createdBy: correctedCreatedBy, - // ); - } - }, - child: Text( + onTap: () async { + final String? correctedCreatedBy = + await shortTextfieldDialog( + title: 'Ersteller ändern', + obscureText: false, + hintText: 'Kürzel eintragen', + labelText: status.createdBy, + context: context, + ); + if (correctedCreatedBy != null) { + // TODO: uncomment when ready + // _learningSupportManager + // .updateSupportCategoryStatusProperty( + // pupil: pupil, + // statusId: status.statusId, + // createdBy: correctedCreatedBy, + // ); + } + }, + child: Text( + status.createdBy, + style: const TextStyle( + color: AppColors.interactiveColor, + fontWeight: FontWeight.bold, + ), + ), + ) + : Text( status.createdBy, style: const TextStyle( - color: AppColors.interactiveColor, fontWeight: FontWeight.bold, ), ), - ) - : Text( - status.createdBy, - style: const TextStyle(fontWeight: FontWeight.bold), - ), ], ), ], diff --git a/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_goal/support_goal_card.dart b/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_goal/support_goal_card.dart index 12e26c66..e29c0432 100644 --- a/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_goal/support_goal_card.dart +++ b/school_data_hub_flutter/lib/features/learning_support/presentation/widgets/support_goal/support_goal_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; import 'package:school_data_hub_flutter/features/learning_support/domain/learning_support_helper.dart'; @@ -15,8 +15,11 @@ final _learningSupportManager = di(); class SupportGoalCard extends StatelessWidget { final PupilProxy pupil; final int goalIndex; - const SupportGoalCard( - {required this.pupil, required this.goalIndex, super.key}); + const SupportGoalCard({ + required this.pupil, + required this.goalIndex, + super.key, + }); @override Widget build(BuildContext context) { @@ -27,9 +30,10 @@ class SupportGoalCard extends StatelessWidget { child: InkWell( onLongPress: () async { final bool? delete = await confirmationDialog( - context: context, - title: 'Förderziel löschen', - message: 'Förderziel löschen?'); + context: context, + title: 'Förderziel löschen', + message: 'Förderziel löschen?', + ); if (delete == true) { // TODO: uncomment when ready // await _learningSupportManager @@ -42,7 +46,9 @@ class SupportGoalCard extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), side: const BorderSide( - color: AppColors.cardInCardBorderColor, width: 2), + color: AppColors.cardInCardBorderColor, + width: 2, + ), ), color: AppColors.cardInCardColor, child: Column( @@ -50,17 +56,24 @@ class SupportGoalCard extends StatelessWidget { const Gap(5), Padding( padding: const EdgeInsets.only( - top: 8.0, bottom: 8, left: 10, right: 10), + top: 8.0, + bottom: 8, + left: 10, + right: 10, + ), child: Container( decoration: BoxDecoration( color: LearningSupportHelper.getRootSupportCategoryColor( - _learningSupportManager.getRootSupportCategory(pupil - .supportGoals![goalIndex].supportCategoryId)), + _learningSupportManager.getRootSupportCategory( + pupil.supportGoals![goalIndex].supportCategoryId, + ), + ), borderRadius: BorderRadius.circular(5.0), ), child: SupportCategoryCardBanner( - categoryId: - pupil.supportGoals![goalIndex].supportCategoryId), + categoryId: + pupil.supportGoals![goalIndex].supportCategoryId, + ), ), ), Padding( @@ -70,15 +83,20 @@ class SupportGoalCard extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(top: 4.0), - child: getLastCategoryStatusSymbol(pupil, - pupil.supportGoals![goalIndex].supportCategoryId), + child: getLastCategoryStatusSymbol( + pupil, + pupil.supportGoals![goalIndex].supportCategoryId, + ), ), const Gap(10), Flexible( child: Text( _learningSupportManager - .getSupportCategory(pupil - .supportGoals![goalIndex].supportCategoryId) + .getSupportCategory( + pupil + .supportGoals![goalIndex] + .supportCategoryId, + ) .name, style: const TextStyle( fontSize: 20, @@ -97,23 +115,26 @@ class SupportGoalCard extends StatelessWidget { child: Text( pupil.supportGoals![goalIndex].description, style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.groupColor), + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.groupColor, + ), ), ), - const Gap(10) + const Gap(10), ], ), const Gap(5), const Row( children: [ Gap(15), - Text('Strategien:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - )), + Text( + 'Strategien:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), ], ), const Gap(5), @@ -123,9 +144,7 @@ class SupportGoalCard extends StatelessWidget { Flexible( child: Text( pupil.supportGoals![goalIndex].strategies, - style: const TextStyle( - fontSize: 16, - ), + style: const TextStyle(fontSize: 16), ), ), const Gap(10), @@ -147,7 +166,7 @@ class SupportGoalCard extends StatelessWidget { Text( pupil.supportGoals![goalIndex].createdAt .toLocal() - .formatForUser(), + .formatDateForUser(), style: const TextStyle(fontWeight: FontWeight.bold), ), ], diff --git a/school_data_hub_flutter/lib/features/learning_support/services/learning_support_plan_pdf_generator.dart b/school_data_hub_flutter/lib/features/learning_support/services/learning_support_plan_pdf_generator.dart index d1d49a9c..1eb1a885 100644 --- a/school_data_hub_flutter/lib/features/learning_support/services/learning_support_plan_pdf_generator.dart +++ b/school_data_hub_flutter/lib/features/learning_support/services/learning_support_plan_pdf_generator.dart @@ -8,7 +8,7 @@ import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_app_bar.dart'; @@ -80,7 +80,7 @@ class LearningSupportPlanPdfGenerator { // Get the proper directory for saving files final directory = await getApplicationDocumentsDirectory(); final fileName = - "Förderplan_${pupil.firstName}_${pupil.lastName}_${DateTime.now().formatForUser()}.pdf"; + "Förderplan_${pupil.firstName}_${pupil.lastName}_${DateTime.now().formatDateForUser()}.pdf"; final file = File('${directory.path}/$fileName'); await file.writeAsBytes(await pdf.save()); diff --git a/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service.dart b/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service.dart index c9e3dd35..58604742 100644 --- a/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service.dart +++ b/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service.dart @@ -105,23 +105,28 @@ class MatrixApiService { ); // Direct messaging methods - Future> sendDirectTextMessage({ - required String targetUserId, - required String text, - String? transactionId, - }) => _roomApiService.sendDirectTextMessage( - targetUserId: targetUserId, - text: text, - transactionId: transactionId, - ); + // Future> sendDirectTextMessage({ + // required String targetUserId, + // required String text, + // String? transactionId, + // }) => _roomApiService.sendDirectTextMessage( + // targetUserId: targetUserId, + // text: text, + // transactionId: transactionId, + // ); Future inviteUserToRoom({ required String roomId, required String userId, }) => _roomApiService.inviteUserToRoom(roomId: roomId, userId: userId); - Future getOrCreateDirectMessageRoom(String targetUserId) => - _roomApiService.getOrCreateDirectMessageRoom(targetUserId); + Future getOrCreateDirectMessageRoom({ + required String targetUserId, + required String currentUserId, + }) => _roomApiService.findOrCreateDirectMessageRoom( + targetUserId: targetUserId, + currentUserId: currentUserId, + ); void setMatrixEnvironmentValues({ required String url, diff --git a/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service_old.dart b/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service_old.dart deleted file mode 100644 index 00511b47..00000000 --- a/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service_old.dart +++ /dev/null @@ -1,522 +0,0 @@ -// import 'dart:convert'; -// import 'dart:io'; - -// import 'package:dio/dio.dart'; -// import 'package:school_data_hub_flutter/common/services/notification_service.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/matrix_policy_helper_functions.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/matrix_policy_manager.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/models/matrix_room.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/models/matrix_user.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/models/policy.dart'; -// import 'package:school_data_hub_flutter/features/matrix/services/api/api_client.dart'; -// import 'package:school_data_hub_flutter/features/matrix/services/api/api_settings.dart'; -// import 'package:watch_it/watch_it.dart'; - -// enum MatrixAuthType { matrix, corporal } - -// enum ChatTypePreset { -// public('public_chat'), -// private('private_chat'), -// trustedPrivate('trusted_private_chat'), -// ; - -// final String value; -// const ChatTypePreset(this.value); -// } - -// final _matrixPolicyManager = di(); - -// final _notificationService = di(); - -// class MatrixApiService { -// final _apiClient = ApiClient(Dio()); - -// String _matrixUrl; -// String _matrixToken; -// String _corporalToken; -// MatrixApiService({ -// required String matrixUrl, -// required String matrixToken, -// required String corporalToken, -// }) : _matrixUrl = matrixUrl, -// _matrixToken = matrixToken, -// _corporalToken = corporalToken { -// _apiClient.setApiOptions( -// tokenKey: Token.matrix, token: 'Bearer $_matrixToken'); -// _apiClient.setApiOptions( -// tokenKey: Token.corporal, token: 'Bearer $_corporalToken'); -// } - -// void setMatrixEnvironmentValues( -// {required String url, -// required String matrixToken, -// required String policyToken}) { -// _matrixUrl = url; -// _matrixToken = matrixToken; -// _corporalToken = policyToken; -// return; -// } - -// // Options _requestOptions({required MatrixAuthType authType}) { -// // final token = -// // authType == MatrixAuthType.matrix ? _matrixToken : _corporalToken; - -// // return Options(headers: {'Authorization': 'Bearer $token'}); -// // } - -// Future fetchMatrixPolicy() async { -// final response = await _apiClient.get( -// '$_matrixUrl/_matrix/corporal/policy', -// options: _apiClient.corporalOptions, -// ); - -// if (response.statusCode != 200) { -// _notificationService.showSnackBar( -// NotificationType.error, 'Fehler: status code ${response.statusCode}'); -// throw ApiException('Fehler beim Laden der Policy', response.statusCode); -// } -// final Policy policy = Policy.fromJson(response.data['policy']); -// _notificationService.showSnackBar( -// NotificationType.success, 'Matrix-Räumeverwaltung geladen'); - -// return policy; -// } - -// //- CREATE ROOM -// // curl --header "Authorization: Bearer " XPOST -d '{ -// // "creation_content": {"m.federate": false}, -// // "is_direct": false, "name": "TestraumNeu3", "preset": "private_chat", -// // "room_alias_name": "testraum3", "topic": "Das ist ein Testraum", "visibility": "private", -// // "power_level_content_override": { -// // "ban": 50, -// // "events": { -// // "m.room.name": 50, -// // "m.room.power_levels": 100, -// // "m.room.history_visibility": 100, -// // "m.room.canonical_alias": 50, -// // "m.room.avatar": 50, -// // "m.room.tombstone": 100, -// // "m.room.server_acl": 100, -// // "m.room.encryption": 100, -// // "m.space.child": 50, -// // "m.room.topic": 50, -// // "m.room.pinned_events": 50, -// // "m.reaction": 65, -// // "m.room.redaction": 0, -// // "org.matrix.msc3401.call": 50, -// // "org.matrix.msc3401.call.member": 50, -// // "im.vector.modular.widgets": 50, -// // "io.element.voice_broadcast_info": 50 -// // }, -// // "events_default": 25, -// // "invite": 100, -// // "kick": 100, -// // "notifications": {"room": 20}, -// // "redact": 50, -// // "state_default": 50, -// // "users": { "@mxcorporal:hermannschule.de": 100}, -// // "users_default": 0} - -// // }' "https://post.hermannschule.de/_matrix/client/v3/createRoom" - -// //- https://spec.matrix.org/v1.10/client-server-api/#creation - -// // XPOST -d '{ -// // "creation_content": { -// // "m.federate": false -// // }, -// // "is_direct": false, -// // "name": "Testraum", -// // "preset": "private_chat", -// // "room_alias_name": "testraum", -// // "topic": "Das ist ein Testraum", -// // "visibility": "private" -// // }' "https://post.hermannschule.de/_matrix/client/v3/createRoom" - -// static const String _createRoom = '/_matrix/client/v3/createRoom'; - -// Future createMatrixRoom( -// {required String name, -// required String topic, -// required ChatTypePreset chatTypePreset, -// String? aliasName}) async { -// MatrixRoom? room; -// //- API: https://spec.matrix.org/latest/client-server-api/#create-room -// final data = jsonEncode({ -// "creation_content": {"m.federate": false}, -// "is_direct": false, -// "name": name, -// "preset": "private_chat", -// if (aliasName != null) "room_alias_name": aliasName, -// "topic": topic, -// "visibility": 'private', -// "power_level_content_override": { -// "users": {"${di().matrixAdmin}": 100}, -// "events": {"m.room.name": 50}, -// "users_default": 0, -// "events_default": 50, -// "state_default": 50, -// "ban": 100, -// "kick": 100, -// "invite": 100, -// "redact": 50 -// } -// }); - -// final Response response = await _apiClient.post( -// '$_matrixUrl$_createRoom', -// data: data, -// options: _apiClient.matrixOptions, -// ); -// if (response.statusCode == 200) { -// // extract the value of "room_id" out of the response -// final String roomId = response.data['room_id']; -// room = await fetchAdditionalRoomInfos(roomId); -// } - -// return room; -// } - -// //- GET ROOMS - -// // Future?> fetchMatrixRooms() async { -// // List rooms = []; -// // // use a custom client with the right token to fetch the policy -// // _dioClient.setCustomDioClientOptions( -// // baseUrl: _matrixUrl, -// // tokenKey: 'Authorization', -// // token: 'Bearer $_synapseToken'); - -// // notificationService.showSnackBar( -// // NotificationType.success, 'Matrix-Räume werden geladen...'); - -// // for (String roomId in policy.managedRoomIds) { -// // MatrixRoom namedRoom = await _fetchAdditionalRoomInfos(roomId); -// // rooms.add(namedRoom); -// // } -// // } - -// //- PUT POLICY - -// static const String _putMatrixPolicy = '/_matrix/corporal/policy'; - -// Future putMatrixPolicy() async { -// final File policyFile = await MatrixPolicyHelper.generatePolicyJsonFile(); -// final bytes = policyFile.readAsBytesSync(); - -// final Response response = await _apiClient.put( -// '$_matrixUrl$_putMatrixPolicy', -// data: bytes, -// options: _apiClient.corporalOptions, -// ); -// //delete file, we don't need it anymore -// // policyFile.deleteSync(); -// if (response.statusCode != 200) { -// _notificationService.showSnackBar( -// NotificationType.error, 'Fehler: status code ${response.statusCode}'); -// throw ApiException('Fehler beim Setzen der Policy', response.statusCode); -// } - -// _notificationService.showSnackBar( -// NotificationType.success, 'Policy erfolgreich gesetzt'); - -// return; -// } - -// //**- MATRIX USER - -// //- PUT MATRIX USER -// String _createMatrixUser(String userId) { -// return '/_synapse/admin/v2/users/$userId'; -// } - -// Future createNewMatrixUser({ -// required String matrixId, -// required String displayName, -// required String password, -// }) async { -// final data = jsonEncode({ -// "user_id": matrixId, -// "password": password, -// "admin": false, -// "displayname": displayName, -// "threepids": [], -// "avatar_url": "" -// }); - -// // Add before your PUT request -// print('Matrix API Request:'); -// print('URL: $_matrixUrl${_createMatrixUser(matrixId)}'); -// print('Data: $data'); -// print('Headers: ${_apiClient.matrixOptions.headers}'); - -// final Response response = await _apiClient.put( -// '$_matrixUrl${_createMatrixUser(matrixId)}', -// data: data, -// options: _apiClient.matrixOptions, -// ); -// // statuscode 201 means: User created -// if (!(response.statusCode == 201 || response.statusCode == 200)) { -// _notificationService.showSnackBar( -// NotificationType.error, 'Fehler: status code ${response.statusCode}'); -// throw ApiException( -// 'Fehler beim Erstellen des Benutzers', response.statusCode); -// } -// final MatrixUser newUser = MatrixUser( -// id: matrixId, -// displayName: displayName, -// joinedRooms: [], -// active: true, -// authType: "passthrough", -// ); -// if (response.statusCode == 201) { -// _notificationService.showSnackBar( -// NotificationType.success, 'Benutzer erstellt'); -// } -// if (response.statusCode == 200) { -// _notificationService.showSnackBar( -// NotificationType.success, 'Deaktivierter Benutzer reaktiviert'); -// } - -// return newUser; -// } - -// //- DELETE USER -// String _deleteMatrixUser(String userId) { -// // return '/_synapse/admin/v2/users/$userId/deactivate'; -// return '/_synapse/admin/v1/deactivate/$userId'; -// } - -// Future deleteMatrixUser(String userId) async { -// final data = jsonEncode({ -// "erase": true, -// }); -// final Response response = await _apiClient.post( -// '$_matrixUrl${_deleteMatrixUser(userId)}', -// data: data, -// options: _apiClient.matrixOptions, -// ); - -// if (response.statusCode != 200) { -// _notificationService.showSnackBar( -// NotificationType.error, 'Fehler: status code ${response.statusCode}'); - -// return false; -// } - -// return true; -// } - -// String _resetPassword(String userId) { -// return '/_synapse/admin/v1/reset_password/$userId'; -// } - -// Future resetPassword( -// {required String userId, -// required String newPassword, -// bool? logoutDevices}) async { -// final data = jsonEncode({ -// "new_password": newPassword, -// "logout_devices": logoutDevices, -// }); - -// final Response response = await _apiClient.post( -// '$_matrixUrl${_resetPassword(userId)}', -// data: data, -// options: _apiClient.matrixOptions, -// ); - -// if (response.statusCode != 200) { -// _notificationService.showSnackBar( -// NotificationType.error, 'Fehler: status code ${response.statusCode}'); - -// return false; -// } - -// return true; -// } - -// //- GET USER -// String _fetchMatrixUser(String userId) { -// return '/_synapse/admin/v2/users/$userId'; -// } - -// Future fetchMatrixUserById(String userId) async { -// final Response response = await _apiClient.get( -// '$_matrixUrl${_fetchMatrixUser(userId)}', -// options: _apiClient.matrixOptions, -// ); - -// if (response.statusCode == 200) { -// final MatrixUser user = MatrixUser.fromJson(response.data); - -// return user; -// } - -// return null; -// } - -// //- PUT - -// //- GET MEDIA -// // https://spec.matrix.org/v1.10/client-server-api/#get_matrixmediav3downloadservernamemediaid -// // GET /_matrix/media/v3/download/{serverName}/{mediaId} -// //- GET ROOM NAME -// // String fetchRoomName(String roomId) { -// // return '_synapse/admin/v1/rooms/$roomId'; -// // } -// String _fetchRoomName(String roomId) { -// return '/_matrix/client/v3/rooms/$roomId/state/m.room.name'; -// //return '_synapse/admin/v1/rooms/$roomId/state'; -// } - -// String _fetchRoomPowerLevelsUrl(String roomId) { -// return '/_matrix/client/v3/rooms/$roomId/state/m.room.power_levels'; -// } - -// Future fetchAdditionalRoomInfos(String roomId) async { -// String? name; -// int? powerLevelReactions; -// int? eventsDefault; -// List? roomAdmins; - -// // First API call -// final responseRoomSPowerLevels = await _apiClient.get( -// // ignore: unnecessary_string_interpolations -// '$_matrixUrl${_fetchRoomPowerLevelsUrl(roomId)}', -// options: _apiClient.matrixOptions, -// ); - -// if (responseRoomSPowerLevels.statusCode == 200) { -// powerLevelReactions = -// responseRoomSPowerLevels.data['events']['m.reaction'] ?? 0; -// eventsDefault = responseRoomSPowerLevels.data['events_default'] ?? 0; - -// if (responseRoomSPowerLevels.data['users'] is Map) { -// final usersMap = -// responseRoomSPowerLevels.data['users'] as Map; -// roomAdmins = usersMap.keys -// .map( -// (userId) => RoomAdmin(id: userId, powerLevel: usersMap[userId])) -// .toList(); -// } -// } -// // if (roomId == '!RHMRhueGNUwEHjoMkm:hermannschule.de') { -// // debugger(); -// // } -// // Second API call -// final responseRoomName = await _apiClient.get( -// // ignore: unnecessary_string_interpolations -// '$_matrixUrl${_fetchRoomName(roomId)}', -// options: _apiClient.matrixOptions, -// ); - -// if (responseRoomName.statusCode == 200) { -// name = responseRoomName.data['name'] ?? 'No Room Name'; -// } - -// MatrixRoom roomWithAdditionalInfos = MatrixRoom( -// id: roomId, -// name: name, -// powerLevelReactions: powerLevelReactions, -// eventsDefault: eventsDefault, -// roomAdmins: roomAdmins, -// ); - -// return roomWithAdditionalInfos; -// } - -// //- PUT ROOM POWER LEVELS -// String _putRoomPowerLevels(String roomId) { -// // ensure that ! and : are properly coded for the url -// final roomIdforUrl = roomId.replaceAllMapped( -// RegExp(r'[!:]'), -// (match) { -// switch (match.group(0)) { -// case '!': -// return '%21'; -// case ':': -// return '%3A'; -// default: -// return match.group(0)!; -// } -// }, -// ); -// return '/_matrix/client/v3/rooms/$roomIdforUrl/state/m.room.power_levels'; -// } - -// Future changeRoomPowerLevels( -// {required String roomId, -// RoomAdmin? newRoomAdmin, -// String? removeAdminWithId, -// int? eventsDefault, -// int? reactions}) async { -// List adminPowerLevels = []; - -// Map adminPowerLevelsMap = {}; - -// final matrixRoom = _matrixPolicyManager.getRoomById(roomId); -// // We make sure that the instance admin has admin power level in the room -// adminPowerLevels = matrixRoom.roomAdmins ?? -// [RoomAdmin(id: _matrixPolicyManager.matrixAdmin!, powerLevel: 100)]; - -// if (newRoomAdmin != null) { -// adminPowerLevels.add(newRoomAdmin); -// } -// if (removeAdminWithId != null) { -// adminPowerLevels.removeWhere((admin) => admin.id == removeAdminWithId); -// } - -// for (RoomAdmin admin in adminPowerLevels) { -// adminPowerLevelsMap[admin.id] = admin.powerLevel; -// } - -// final data = jsonEncode({ -// "ban": 50, -// "events": { -// "m.room.name": 50, -// "m.room.power_levels": 100, -// "m.room.history_visibility": 100, -// "m.room.canonical_alias": 50, -// "m.room.avatar": 50, -// "m.room.tombstone": 100, -// "m.room.server_acl": 100, -// "m.room.encryption": 100, -// "m.space.child": 50, -// "m.room.topic": 50, -// "m.room.pinned_events": 50, -// "m.reaction": reactions ?? matrixRoom.powerLevelReactions, -// "m.room.redaction": 0, -// "org.matrix.msc3401.call": 50, -// "org.matrix.msc3401.call.member": 50, -// "im.vector.modular.widgets": 50, -// "io.element.voice_broadcast_info": 50 -// }, -// "events_default": eventsDefault ?? matrixRoom.eventsDefault, -// "invite": 50, -// "kick": 50, -// "notifications": {"room": 20}, -// "redact": 50, -// "state_default": 50, -// "users": adminPowerLevelsMap, -// "users_default": 0 -// }); - -// final Response response = await _apiClient.put( -// '$_matrixUrl${_putRoomPowerLevels(roomId)}', -// data: data, -// options: _apiClient.matrixOptions, -// ); - -// if (response.statusCode != 200) { -// _notificationService.showSnackBar( -// NotificationType.error, 'Fehler: status code ${response.statusCode}'); -// throw ApiException( -// 'Fehler beim Setzen der Power Levels', response.statusCode); -// } - -// final MatrixRoom room = await fetchAdditionalRoomInfos(roomId); - -// return room; -// } -// } diff --git a/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service_refactored.dart b/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service_refactored.dart deleted file mode 100644 index 3690d419..00000000 --- a/school_data_hub_flutter/lib/features/matrix/data/matrix_api_service_refactored.dart +++ /dev/null @@ -1,196 +0,0 @@ -// import 'dart:io'; - -// import 'package:dio/dio.dart'; -// import 'package:school_data_hub_flutter/common/services/notification_service.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/matrix_policy_helper_functions.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/models/matrix_room.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/models/matrix_user.dart'; -// import 'package:school_data_hub_flutter/features/matrix/domain/models/policy.dart'; -// import 'package:school_data_hub_flutter/features/matrix/rooms/data/matrix_room_api_service.dart' -// as room_api; -// import 'package:school_data_hub_flutter/features/matrix/services/api/api_client.dart'; -// import 'package:school_data_hub_flutter/features/matrix/services/api/api_settings.dart'; -// import 'package:school_data_hub_flutter/features/matrix/users/data/matrix_user_api_service.dart'; -// import 'package:watch_it/watch_it.dart'; - -// enum MatrixAuthType { matrix, corporal } - -// class MatrixApiService { -// final _apiClient = ApiClient(Dio()); -// final _notificationService = di(); - -// String _matrixUrl; -// String _matrixToken; -// String _corporalToken; - -// // Sub-services for users and rooms -// late final MatrixUserApiService _userApiService; -// late final room_api.MatrixRoomApiService _roomApiService; - -// MatrixApiService({ -// required String matrixUrl, -// required String matrixToken, -// required String corporalToken, -// }) : _matrixUrl = matrixUrl, -// _matrixToken = matrixToken, -// _corporalToken = corporalToken { -// _apiClient.setApiOptions( -// tokenKey: Token.matrix, token: 'Bearer $_matrixToken'); -// _apiClient.setApiOptions( -// tokenKey: Token.corporal, token: 'Bearer $_corporalToken'); - -// // Initialize sub-services with shared ApiClient -// _userApiService = MatrixUserApiService( -// apiClient: _apiClient, -// matrixUrl: matrixUrl, -// matrixToken: matrixToken, -// corporalToken: corporalToken, -// ); -// _roomApiService = room_api.MatrixRoomApiService( -// apiClient: _apiClient, -// matrixUrl: matrixUrl, -// matrixToken: matrixToken, -// corporalToken: corporalToken, -// ); -// } - -// // Getters to access sub-services -// MatrixUserApiService get userApi => _userApiService; -// room_api.MatrixRoomApiService get roomApi => _roomApiService; - -// void setMatrixEnvironmentValues({ -// required String url, -// required String matrixToken, -// required String policyToken, -// }) { -// _matrixUrl = url; -// _matrixToken = matrixToken; -// _corporalToken = policyToken; - -// // Update sub-services as well -// _userApiService.setMatrixEnvironmentValues( -// url: url, -// matrixToken: matrixToken, -// policyToken: policyToken, -// ); -// _roomApiService.setMatrixEnvironmentValues( -// url: url, -// matrixToken: matrixToken, -// policyToken: policyToken, -// ); -// return; -// } - -// //- POLICY OPERATIONS - -// Future fetchMatrixPolicy() async { -// final response = await _apiClient.get( -// '$_matrixUrl/_matrix/corporal/policy', -// options: _apiClient.corporalOptions, -// ); - -// if (response.statusCode != 200) { -// _notificationService.showSnackBar( -// NotificationType.error, 'Fehler: status code ${response.statusCode}'); -// throw ApiException('Fehler beim Laden der Policy', response.statusCode); -// } -// final Policy policy = Policy.fromJson(response.data['policy']); -// _notificationService.showSnackBar( -// NotificationType.success, 'Matrix-Räumeverwaltung geladen'); - -// return policy; -// } - -// static const String _putMatrixPolicy = '/_matrix/corporal/policy'; - -// Future putMatrixPolicy() async { -// final File policyFile = await MatrixPolicyHelper.generatePolicyJsonFile(); -// final bytes = policyFile.readAsBytesSync(); - -// final Response response = await _apiClient.put( -// '$_matrixUrl$_putMatrixPolicy', -// data: bytes, -// options: _apiClient.corporalOptions, -// ); -// //delete file, we don't need it anymore -// // policyFile.deleteSync(); -// if (response.statusCode != 200) { -// _notificationService.showSnackBar( -// NotificationType.error, 'Fehler: status code ${response.statusCode}'); -// throw ApiException('Fehler beim Setzen der Policy', response.statusCode); -// } - -// _notificationService.showSnackBar( -// NotificationType.success, 'Policy erfolgreich gesetzt'); - -// return; -// } - -// //- DELEGATION METHODS FOR BACKWARD COMPATIBILITY -// // These methods delegate to the appropriate sub-services to maintain existing API - -// // User API delegation -// Future createNewMatrixUser({ -// required String matrixId, -// required String displayName, -// required String password, -// }) => -// _userApiService.createNewMatrixUser( -// matrixId: matrixId, -// displayName: displayName, -// password: password, -// ); - -// Future deleteMatrixUser(String userId) => -// _userApiService.deleteMatrixUser(userId); - -// Future resetPassword({ -// required String userId, -// required String newPassword, -// bool? logoutDevices, -// }) => -// _userApiService.resetPassword( -// userId: userId, -// newPassword: newPassword, -// logoutDevices: logoutDevices, -// ); - -// Future fetchMatrixUserById(String userId) => -// _userApiService.fetchMatrixUserById(userId); - -// // Room API delegation -// Future createMatrixRoom({ -// required String name, -// required String topic, -// required room_api.ChatTypePreset chatTypePreset, -// String? aliasName, -// }) => -// _roomApiService.createMatrixRoom( -// name: name, -// topic: topic, -// chatTypePreset: chatTypePreset, -// aliasName: aliasName, -// ); - -// Future fetchAdditionalRoomInfos(String roomId) => -// _roomApiService.fetchAdditionalRoomInfos(roomId); - -// Future changeRoomPowerLevels({ -// required String roomId, -// RoomAdmin? newRoomAdmin, -// String? removeAdminWithId, -// int? eventsDefault, -// int? reactions, -// required MatrixRoom currentRoom, -// required String matrixAdmin, -// }) => -// _roomApiService.changeRoomPowerLevels( -// roomId: roomId, -// newRoomAdmin: newRoomAdmin, -// adminIdToRemove: removeAdminWithId, -// eventsDefault: eventsDefault, -// reactions: reactions, -// currentRoom: currentRoom, -// matrixAdmin: matrixAdmin, -// ); -// } diff --git a/school_data_hub_flutter/lib/features/matrix/domain/matrix_policy_manager.dart b/school_data_hub_flutter/lib/features/matrix/domain/matrix_policy_manager.dart index 8c9892d9..77a78ac1 100644 --- a/school_data_hub_flutter/lib/features/matrix/domain/matrix_policy_manager.dart +++ b/school_data_hub_flutter/lib/features/matrix/domain/matrix_policy_manager.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; @@ -219,23 +218,41 @@ class MatrixPolicyManager extends ChangeNotifier { required String text, String? transactionId, }) async { - log('MatrixPolicyManager.sendDirectTextMessage called'); - log('targetUserId: $targetUserId'); - log('text: $text'); - log('transactionId: $transactionId'); + _log.info('Sending direct text message to $targetUserId'); + _log.info('targetUserId: $targetUserId'); + _log.info('text: $text'); + _log.info('transactionId: $transactionId'); try { - final result = await _matrixApiService.sendDirectTextMessage( + // First, check if there's already an existing direct message room + // This checks both the sender's and receiver's account data + _log.info('Checking for existing direct message room...'); + final roomId = await _matrixApiService.getOrCreateDirectMessageRoom( targetUserId: targetUserId, + currentUserId: _matrixAdminId!, + ); + _log.info('Using room ID: $roomId'); + + // Ensure admin is in the room before sending + // (This is handled internally by the API service, but we log it here for clarity) + _log.info('Sending message to existing/created room: $roomId'); + + // Send the message to the room + final response = await _matrixApiService.sendTextMessage( + roomId: roomId, text: text, transactionId: transactionId, ); - log('MatrixPolicyManager.sendDirectTextMessage result: $result'); + final result = {'eventId': response.eventId, 'roomId': roomId}; + + _log.info('MatrixPolicyManager.sendDirectTextMessage result: $result'); return result; } catch (e, stackTrace) { - log('MatrixPolicyManager.sendDirectTextMessage error: $e'); - log('MatrixPolicyManager.sendDirectTextMessage stackTrace: $stackTrace'); + _log.severe('MatrixPolicyManager.sendDirectTextMessage error: $e'); + _log.severe( + 'MatrixPolicyManager.sendDirectTextMessage stackTrace: $stackTrace', + ); rethrow; } } diff --git a/school_data_hub_flutter/lib/features/matrix/rooms/data/matrix_room_api_service.dart b/school_data_hub_flutter/lib/features/matrix/rooms/data/matrix_room_api_service.dart index fa77c5b2..071e644a 100644 --- a/school_data_hub_flutter/lib/features/matrix/rooms/data/matrix_room_api_service.dart +++ b/school_data_hub_flutter/lib/features/matrix/rooms/data/matrix_room_api_service.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:developer'; import 'package:dio/dio.dart'; +import 'package:logging/logging.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/features/matrix/domain/matrix_policy_manager.dart'; import 'package:school_data_hub_flutter/features/matrix/domain/models/matrix_message.dart'; @@ -22,6 +23,7 @@ enum ChatTypePreset { class MatrixRoomApiService { final ApiClient _apiClient; final _notificationService = di(); + final _log = Logger('MatrixRoomApiService'); final String _matrixUrl; @@ -281,7 +283,7 @@ class MatrixRoomApiService { final messageData = {'msgtype': 'm.text', 'body': message.body}; try { - log('Sending message to room: $roomId'); + _log.info('Sending message to room: $roomId'); log('Endpoint: $endpoint'); log('Message data: ${jsonEncode(messageData)}'); log('Message data type: ${messageData.runtimeType}'); @@ -480,60 +482,51 @@ class MatrixRoomApiService { /// [transactionId] - Optional transaction ID for deduplication /// /// Returns the event ID of the sent message and the room ID - Future> sendDirectMessage({ - required String targetUserId, - required MatrixMessage message, - String? transactionId, - }) async { - // Step 1: Create direct message room - final roomId = await _createDirectMessageRoom(targetUserId); - - // Step 2: Ensure the admin account is in the room before sending - await _ensureAdminInRoom(roomId); - - // Step 3: Send message to the created room - final response = await sendMessage( - roomId: roomId, - message: message, - transactionId: transactionId, - ); - - return {'eventId': response.eventId, 'roomId': roomId}; - } + // Future> sendDirectMessage({ + // required String targetUserId, + // required MatrixMessage message, + // String? transactionId, + // }) async { + // // Step 1: Create direct message room + // final roomId = await _createDirectMessageRoom(targetUserId); + + // // Step 2: Ensure the admin account is in the room before sending + // await _ensureAdminInRoom(roomId); + + // // Step 3: Send message to the created room + // final response = await sendMessage( + // roomId: roomId, + // message: message, + // transactionId: transactionId, + // ); + + // return {'eventId': response.eventId, 'roomId': roomId}; + // } /// Creates a direct message room with a specific user /// First checks if a direct message room already exists with this user - Future _createDirectMessageRoom(String targetUserId) async { + Future _findOrcreateDirectMessageRoom({ + required String targetUserId, + required String currentUserId, + }) async { log('_createDirectMessageRoom called for: $targetUserId'); try { - // Get current user info first - final Response whoamiResponse = await _apiClient.get( - '$_matrixUrl/_matrix/client/v3/account/whoami', - options: _apiClient.matrixOptions, - ); - - if (whoamiResponse.statusCode != 200) { - throw ApiException( - 'Failed to get current user info', - whoamiResponse.statusCode, - ); - } - - final currentUserId = whoamiResponse.data['user_id'] as String; - log('Current admin user: $currentUserId'); - // First, try to find an existing direct message room log('🔍 CHECKING FOR EXISTING DIRECT MESSAGE ROOM...'); - final existingRoomId = await _findExistingDirectMessageRoom(targetUserId); + final existingRoomId = await _findExistingDirectMessageRoom( + targetUserId: targetUserId, + currentUserId: currentUserId, + ); if (existingRoomId != null) { log('✅ FOUND EXISTING ROOM: $existingRoomId'); return existingRoomId; } - log('❌ NO EXISTING ROOM FOUND, CREATING NEW ADMIN-USER ROOM...'); + log('❌ NO EXISTING ROOM FOUND, CREATING NEW DIRECT MESSAGE ROOM...'); // If no existing room found, create a new one with specific power levels final data = jsonEncode({ + "is_direct": true, "name": "Schuldaten Benachrichtigungen", "invite": [targetUserId], "preset": "private_chat", @@ -572,9 +565,9 @@ class MatrixRoomApiService { }, }); - log('Creating room with data: $data'); - log('Matrix URL: $_matrixUrl'); - log('API Options: ${_apiClient.matrixOptions.headers}'); + _log.info('Creating room with data: $data'); + _log.info('Matrix URL: $_matrixUrl'); + _log.info('API Options: ${_apiClient.matrixOptions.headers}'); final Response response = await _apiClient.post( '$_matrixUrl/_matrix/client/v3/createRoom', @@ -619,54 +612,101 @@ class MatrixRoomApiService { /// Finds an existing direct message room with a specific user /// Uses the m.direct account data which is the standard Matrix approach - Future _findExistingDirectMessageRoom(String targetUserId) async { - log('🔍 _findExistingDirectMessageRoom called for: $targetUserId'); + /// Checks both the sender's (admin's) and receiver's (target user's) account data + Future _findExistingDirectMessageRoom({ + required String targetUserId, + required String currentUserId, + }) async { + _log.info('🔍 _findExistingDirectMessageRoom called for: $targetUserId'); try { - // Get current user info - log('Getting current user info...'); - final Response whoamiResponse = await _apiClient.get( - '$_matrixUrl/_matrix/client/v3/account/whoami', - options: _apiClient.matrixOptions, + // First, check the admin's (sender's) m.direct account data + _log.info( + '🔍 Checking m.direct account data for existing direct message rooms...', + ); + final existingRoomId = await _checkDirectRoomsInAccountData( + targetUserId: targetUserId, + currentUserId: currentUserId, ); - if (whoamiResponse.statusCode != 200) { - log('Whoami failed, returning null'); - return null; + if (existingRoomId != null) { + _log.info( + '✅ Found existing room in admin\'s account data: $existingRoomId', + ); + return existingRoomId; } - final currentUserId = whoamiResponse.data['user_id'] as String; - log('Current user ID: $currentUserId'); + // If not found in admin's account data, check the receiver's (target user's) account data + _log.info( + '🔍 No room found in admin\'s account data, checking receiver\'s m.direct account data...', + ); + // final receiverRoomId = await _checkDirectRoomsInAccountData( + // targetUserId: + // targetUserId, // The target user's m.direct will have currentUserId as key + // currentUserId: currentUserId, + // ); + + // if (receiverRoomId != null) { + // _log.info( + // '✅ Found existing room in receiver\'s account data: $receiverRoomId', + // ); + // return receiverRoomId; + // } + + _log.info( + '❌ No existing direct message room found in either account - will create new one', + ); + return null; + } catch (e, stackTrace) { + _log.info('_findExistingDirectMessageRoom error: $e'); + _log.info('_findExistingDirectMessageRoom stackTrace: $stackTrace'); + // If we can't find existing rooms, we'll create a new one + return null; + } + } - // Simple approach: Check if we have any existing direct message rooms with this user - // If we do, we'll use the first one (assuming it's the "Schuldaten Benachrichtigungen" room) - log('🔍 Checking for existing direct message rooms with target user...'); + /// Checks m.direct account data for a specific user and validates existing rooms + /// [userId] - The user whose account data to check + /// [targetUserId] - The user ID to look for in the m.direct data (the other participant) + /// [currentUserId] - The current admin user ID for validation + Future _checkDirectRoomsInAccountData({ + required String targetUserId, + required String currentUserId, + }) async { + try { + _log.info('🔍 Checking m.direct account data for user: $currentUserId'); final Response accountDataResponse = await _apiClient.get( '$_matrixUrl/_matrix/client/v3/user/$currentUserId/account_data/m.direct', options: _apiClient.matrixOptions, ); - log('📊 m.direct response status: ${accountDataResponse.statusCode}'); - log('📊 m.direct response data: ${accountDataResponse.data}'); + _log.info( + '📊 m.direct response status: ${accountDataResponse.statusCode}', + ); + _log.info('📊 m.direct response data: ${accountDataResponse.data}'); if (accountDataResponse.statusCode == 200) { final directRooms = accountDataResponse.data as Map; - log('📊 All direct rooms: ${directRooms.keys.toList()}'); - log('📊 Looking for target user: $targetUserId'); + _log.info('📊 All direct rooms: ${directRooms.keys.toList()}'); + _log.info('📊 Looking for target user: $targetUserId'); if (directRooms.containsKey(targetUserId)) { final roomIds = directRooms[targetUserId] as List; - log( + _log.info( 'Found ${roomIds.length} direct message rooms with target user: $roomIds', ); // Try all existing rooms until we find one we can access if (roomIds.isNotEmpty) { - log('🔍 Trying ${roomIds.length} existing direct message rooms...'); + _log.info( + '🔍 Trying ${roomIds.length} existing direct message rooms...', + ); for (int i = 0; i < roomIds.length; i++) { final String existingRoomId = roomIds[i] as String; - log('🔍 Trying room ${i + 1}/${roomIds.length}: $existingRoomId'); + _log.info( + '🔍 Trying room ${i + 1}/${roomIds.length}: $existingRoomId', + ); // Quick verification: check if we can still access this room try { @@ -685,7 +725,7 @@ class MatrixRoomApiService { .map((member) => member['state_key'] as String) .toList(); - log( + _log.info( '✅ Room $existingRoomId has ${joinedMembers.length} joined members: $joinedMembers', ); @@ -694,7 +734,9 @@ class MatrixRoomApiService { joinedMembers.contains(currentUserId) && joinedMembers.contains(targetUserId)) { // Check if this room has the correct power levels for admin-only messaging - log('🔍 Checking power levels for room: $existingRoomId'); + _log.info( + '🔍 Checking power levels for room: $existingRoomId', + ); final powerLevelsValid = await _checkRoomPowerLevels( existingRoomId, currentUserId, @@ -702,93 +744,111 @@ class MatrixRoomApiService { ); if (powerLevelsValid) { - log( + _log.info( '🎯 Found valid 2-person room with correct power levels: $existingRoomId', ); return existingRoomId; } else { - log( + _log.info( '⚠️ Room $existingRoomId has incorrect power levels (user can write), trying next...', ); } } else { - log( + _log.info( '⚠️ Room $existingRoomId is not a valid 2-person room, trying next...', ); } } else { - log( - '❌ Cannot access room $existingRoomId (status: ${membersResponse.statusCode}), trying next...', + _log.info( + '❌ Cannot access room $existingRoomId (status: ${membersResponse.statusCode}), leaving room...', + ); + // Leave the room if we can't access it + await _apiClient.delete( + '$_matrixUrl/_matrix/client/v3/rooms/$existingRoomId', + options: _apiClient.matrixOptions, ); + _log.info('Room left successfully'); } } catch (e) { - log( + _log.info( '❌ Error checking room $existingRoomId: $e, trying next...', ); } } - log( + _log.info( '❌ None of the ${roomIds.length} existing rooms are accessible or valid', ); } } else { - log( + _log.info( '❌ No direct message rooms found with target user: $targetUserId', ); } } else { - log( - '❌ Failed to get m.direct account data: ${accountDataResponse.statusCode}', + _log.info( + '❌ Failed to get m.direct account data for user $currentUserId: ${accountDataResponse.statusCode}', ); } - log('❌ No existing direct message room found - will create new one'); return null; } catch (e, stackTrace) { - log('_findExistingDirectMessageRoom error: $e'); - log('_findExistingDirectMessageRoom stackTrace: $stackTrace'); - // If we can't find existing rooms, we'll create a new one + _log.info( + '_checkDirectRoomsInAccountData error for user $currentUserId: $e', + ); + _log.info('_checkDirectRoomsInAccountData stackTrace: $stackTrace'); return null; } } /// Sends a direct text message to a user (creates room if needed) - Future> sendDirectTextMessage({ - required String targetUserId, - required String text, - String? transactionId, - }) async { - log('MatrixRoomApiService.sendDirectTextMessage called'); - log('targetUserId: $targetUserId'); - log('text: $text'); - log('transactionId: $transactionId'); - - try { - final message = MatrixTextMessage(body: text); - log('Created MatrixTextMessage: ${message.toJson()}'); - log('Message msgtype: ${message.msgtype}'); - log('Message body: ${message.body}'); - - final result = await sendDirectMessage( - targetUserId: targetUserId, - message: message, - transactionId: transactionId, - ); - - log('MatrixRoomApiService.sendDirectTextMessage result: $result'); - return result; - } catch (e, stackTrace) { - log('MatrixRoomApiService.sendDirectTextMessage error: $e'); - log('MatrixRoomApiService.sendDirectTextMessage stackTrace: $stackTrace'); - rethrow; - } - } + // Future> sendDirectTextMessage({ + // required String targetUserId, + // required String text, + // String? transactionId, + // }) async { + // _log.info('MatrixRoomApiService.sendDirectTextMessage called'); + // _log.info('targetUserId: $targetUserId'); + // _log.info('text: $text'); + // _log.info('transactionId: $transactionId'); + + // try { + // final message = MatrixTextMessage(body: text); + // _log.info('Created MatrixTextMessage: ${message.toJson()}'); + // _log.info('Message msgtype: ${message.msgtype}'); + // _log.info('Message body: ${message.body}'); + + // final result = await sendDirectMessage( + // targetUserId: targetUserId, + // message: message, + // transactionId: transactionId, + // ); + + // _log.info('MatrixRoomApiService.sendDirectTextMessage result: $result'); + // return result; + // } catch (e, stackTrace) { + // _log.info('MatrixRoomApiService.sendDirectTextMessage error: $e'); + // _log.info( + // 'MatrixRoomApiService.sendDirectTextMessage stackTrace: $stackTrace', + // ); + // rethrow; + // } + // } /// Gets or creates a direct message room with a specific user /// Returns the room ID whether it's existing or newly created - Future getOrCreateDirectMessageRoom(String targetUserId) async { - return await _createDirectMessageRoom(targetUserId); + /// Also ensures the admin is in the room before returning + Future findOrCreateDirectMessageRoom({ + required String targetUserId, + required String currentUserId, + }) async { + final roomId = await _findOrcreateDirectMessageRoom( + targetUserId: targetUserId, + currentUserId: currentUserId, + ); + // Ensure admin is in the room (important when using existing rooms) + await _ensureAdminInRoom(roomId); + return roomId; } /// Invites a user to an existing room @@ -833,7 +893,7 @@ class MatrixRoomApiService { String targetUserId, ) async { try { - log('Checking power levels for room: $roomId'); + _log.info('Checking power levels for room: $roomId'); final Response response = await _apiClient.get( '$_matrixUrl/_matrix/client/v3/rooms/$roomId/state/m.room.power_levels', @@ -847,7 +907,7 @@ class MatrixRoomApiService { final adminPowerLevel = users[adminUserId] as int? ?? 0; final userPowerLevel = users[targetUserId] as int? ?? 0; - log( + _log.info( 'Admin power level: $adminPowerLevel, User power level: $userPowerLevel', ); @@ -855,22 +915,22 @@ class MatrixRoomApiService { final isValid = adminPowerLevel >= 50 && userPowerLevel == 0; if (isValid) { - log('Power levels are correct for admin-user communication'); + _log.info('Power levels are correct for admin-user communication'); } else { - log( + _log.info( 'Power levels are incorrect - admin: $adminPowerLevel, user: $userPowerLevel', ); } return isValid; } else { - log( + _log.info( 'Failed to get power levels for room $roomId: ${response.statusCode}', ); return false; } } catch (e) { - log('Error checking power levels for room $roomId: $e'); + _log.info('Error checking power levels for room $roomId: $e'); return false; } } @@ -896,7 +956,7 @@ class MatrixRoomApiService { Map directRooms = {}; if (getResponse.statusCode == 200) { directRooms = Map.from(getResponse.data ?? {}); - log('📊 Current m.direct data: $directRooms'); + _log.info('📊 Current m.direct data: $directRooms'); } // Add this room to the direct chat list for the target user @@ -907,14 +967,16 @@ class MatrixRoomApiService { if (!existingRooms.contains(roomId)) { existingRooms.add(roomId); directRooms[targetUserId] = existingRooms; - log('📊 Added room to existing direct chat list for $targetUserId'); + _log.info( + '📊 Added room to existing direct chat list for $targetUserId', + ); } else { - log('📊 Room already in direct chat list for $targetUserId'); + _log.info('📊 Room already in direct chat list for $targetUserId'); return; } } else { directRooms[targetUserId] = [roomId]; - log('📊 Created new direct chat entry for $targetUserId'); + _log.info('📊 Created new direct chat entry for $targetUserId'); } // Update m.direct account data @@ -925,17 +987,17 @@ class MatrixRoomApiService { ); if (putResponse.statusCode == 200) { - log( + _log.info( '✅ Successfully marked room $roomId as direct chat with $targetUserId', ); } else { - log( + _log.info( '❌ Failed to mark room as direct chat: ${putResponse.statusCode} - ${putResponse.data}', ); } } } catch (e) { - log('❌ Error marking room as direct chat: $e'); + _log.info('❌ Error marking room as direct chat: $e'); } } @@ -962,36 +1024,38 @@ class MatrixRoomApiService { if (accountDataResponse.statusCode == 200) { final directRooms = accountDataResponse.data as Map; - log('📊 Current m.direct data: $directRooms'); + _log.info('📊 Current m.direct data: $directRooms'); if (directRooms.containsKey(targetUserId)) { final roomIds = directRooms[targetUserId] as List; if (roomIds.contains(roomId)) { - log('✅ Room $roomId IS marked as direct chat with $targetUserId'); + _log.info( + '✅ Room $roomId IS marked as direct chat with $targetUserId', + ); } else { - log( + _log.info( '❌ Room $roomId is NOT marked as direct chat with $targetUserId', ); - log('📊 Direct chat rooms with $targetUserId: $roomIds'); + _log.info('📊 Direct chat rooms with $targetUserId: $roomIds'); } } else { - log('❌ No direct chat rooms found with $targetUserId'); + _log.info('❌ No direct chat rooms found with $targetUserId'); } } else { - log( + _log.info( '❌ Failed to get m.direct data: ${accountDataResponse.statusCode}', ); } } } catch (e) { - log('❌ Error checking if room is marked as direct chat: $e'); + _log.info('❌ Error checking if room is marked as direct chat: $e'); } } /// Ensures the admin account is in the room before sending messages /// This prevents 403 Forbidden errors when trying to send to existing rooms Future _ensureAdminInRoom(String roomId) async { - log('_ensureAdminInRoom called for room: $roomId'); + _log.info('_ensureAdminInRoom called for room: $roomId'); try { // Get current user info @@ -1001,12 +1065,14 @@ class MatrixRoomApiService { ); if (whoamiResponse.statusCode != 200) { - log('Failed to get current user info: ${whoamiResponse.statusCode}'); + _log.info( + 'Failed to get current user info: ${whoamiResponse.statusCode}', + ); return; } final currentUserId = whoamiResponse.data['user_id'] as String; - log('Current admin user: $currentUserId'); + _log.info('Current admin user: $currentUserId'); // Check if the admin is already in the room final Response membersResponse = await _apiClient.get( @@ -1023,30 +1089,30 @@ class MatrixRoomApiService { ); if (isAdminInRoom) { - log('Admin is already in room: $roomId'); + _log.info('Admin is already in room: $roomId'); return; } } // If admin is not in the room, try to join it - log('Admin not in room, attempting to join...'); + _log.info('Admin not in room, attempting to join...'); final Response joinResponse = await _apiClient.post( '$_matrixUrl/_matrix/client/v3/rooms/$roomId/join', options: _apiClient.matrixOptions, ); if (joinResponse.statusCode == 200) { - log('Successfully joined room: $roomId'); + _log.info('Successfully joined room: $roomId'); } else { - log( + _log.info( 'Failed to join room: ${joinResponse.statusCode} - ${joinResponse.data}', ); // Don't throw here, let the message sending attempt proceed // The error will be caught when trying to send the message } } catch (e, stackTrace) { - log('_ensureAdminInRoom error: $e'); - log('_ensureAdminInRoom stackTrace: $stackTrace'); + _log.info('_ensureAdminInRoom error: $e'); + _log.info('_ensureAdminInRoom stackTrace: $stackTrace'); // Don't throw here, let the message sending attempt proceed } } diff --git a/school_data_hub_flutter/lib/features/matrix/services/api/api_settings.dart b/school_data_hub_flutter/lib/features/matrix/services/api/api_settings.dart index ce4cff94..22797cfd 100644 --- a/school_data_hub_flutter/lib/features/matrix/services/api/api_settings.dart +++ b/school_data_hub_flutter/lib/features/matrix/services/api/api_settings.dart @@ -1,5 +1,3 @@ -export '../../data/matrix_api_service_old.dart'; - class ApiSettings { // dev environment urls: //static const baseUrl = 'http://10.0.2.2:5000/api'; // android VM diff --git a/school_data_hub_flutter/lib/features/matrix/services/matrix_bulk_credentials_service.dart b/school_data_hub_flutter/lib/features/matrix/services/matrix_bulk_credentials_service.dart index 8a27e003..691ce46c 100644 --- a/school_data_hub_flutter/lib/features/matrix/services/matrix_bulk_credentials_service.dart +++ b/school_data_hub_flutter/lib/features/matrix/services/matrix_bulk_credentials_service.dart @@ -4,7 +4,7 @@ import 'package:flutter/services.dart' show rootBundle; import 'package:logging/logging.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:school_data_hub_flutter/app_utils/custom_encrypter.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/features/matrix/domain/matrix_policy_manager.dart'; import 'package:school_data_hub_flutter/features/matrix/domain/models/matrix_user.dart'; import 'package:watch_it/watch_it.dart'; @@ -106,8 +106,8 @@ class MatrixBulkCredentialsService { // Calculate how many pages we need final int credentialsPerPage = 18; - final int totalPages = - (userCredentials.length / credentialsPerPage).ceil(); + final int totalPages = (userCredentials.length / credentialsPerPage) + .ceil(); _log.info( 'Creating $totalPages pages with $credentialsPerPage credentials per page', @@ -132,15 +132,14 @@ class MatrixBulkCredentialsService { horizontal: 15, vertical: 10, ), // Reduced horizontal margins for bigger cards - build: - (pw.Context context) => _buildCredentialsPage( - image: image, - pageCredentials: pageCredentials, - domain: domain, - isStaff: isStaff, - pageNumber: pageIndex + 1, - totalPages: totalPages, - ), + build: (pw.Context context) => _buildCredentialsPage( + image: image, + pageCredentials: pageCredentials, + domain: domain, + isStaff: isStaff, + pageNumber: pageIndex + 1, + totalPages: totalPages, + ), ), ); } @@ -352,7 +351,7 @@ class MatrixBulkCredentialsService { // Creation date inside the card pw.Center( child: pw.Text( - 'Erstellt: ${DateTime.now().formatForUser()}', + 'Erstellt: ${DateTime.now().formatDateForUser()}', style: const pw.TextStyle(fontSize: 9), ), ), diff --git a/school_data_hub_flutter/lib/features/matrix/services/matrix_credentials_pdf_generator.dart b/school_data_hub_flutter/lib/features/matrix/services/matrix_credentials_pdf_generator.dart index ecc017fb..85a39a95 100644 --- a/school_data_hub_flutter/lib/features/matrix/services/matrix_credentials_pdf_generator.dart +++ b/school_data_hub_flutter/lib/features/matrix/services/matrix_credentials_pdf_generator.dart @@ -4,7 +4,7 @@ import 'package:flutter/services.dart' show rootBundle; import 'package:logging/logging.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:school_data_hub_flutter/app_utils/custom_encrypter.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/features/matrix/domain/models/matrix_user.dart'; final _log = Logger('MatrixCredentialsPrinter'); @@ -45,111 +45,109 @@ class MatrixCredentialsPrinter { pdf.addPage( pw.Page( - build: - (pw.Context context) => pw.Center( - child: pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.start, - children: [ - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, + build: (pw.Context context) => pw.Center( + child: pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.start, + children: [ + pw.Expanded( + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.start, children: [ - pw.Row( + pw.Image(image, width: 40, height: 40), + // pw.SizedBox(width: 10), + pw.Column( mainAxisAlignment: pw.MainAxisAlignment.start, children: [ - pw.Image(image, width: 40, height: 40), - // pw.SizedBox(width: 10), - pw.Column( - mainAxisAlignment: pw.MainAxisAlignment.start, - children: [ - pw.Text( - 'Hermannpost', - style: pw.TextStyle( - fontSize: 20, - fontWeight: pw.FontWeight.bold, - ), - ), - pw.Text( - 'Zugangsdaten - gut aufbewahren!', - style: pw.TextStyle( - fontSize: 8, - fontWeight: pw.FontWeight.bold, - ), - ), - ], + pw.Text( + 'Hermannpost', + style: pw.TextStyle( + fontSize: 20, + fontWeight: pw.FontWeight.bold, + ), + ), + pw.Text( + 'Zugangsdaten - gut aufbewahren!', + style: pw.TextStyle( + fontSize: 8, + fontWeight: pw.FontWeight.bold, + ), ), ], ), - pw.SizedBox(height: 5), - pw.Text( - matrixUser.displayName, - style: pw.TextStyle( - fontSize: 16, - fontWeight: pw.FontWeight.bold, - ), + ], + ), + pw.SizedBox(height: 5), + pw.Text( + matrixUser.displayName, + style: pw.TextStyle( + fontSize: 16, + fontWeight: pw.FontWeight.bold, + ), + ), + pw.SizedBox(height: 5), + pw.Row( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.BarcodeWidget( + data: encryptedQrCodeData, + width: 60, + height: 60, + barcode: pw.Barcode.qrCode(), + drawText: false, ), - pw.SizedBox(height: 5), - pw.Row( + pw.SizedBox(width: 10), + pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, + mainAxisAlignment: pw.MainAxisAlignment.spaceAround, children: [ - pw.BarcodeWidget( - data: encryptedQrCodeData, - width: 60, - height: 60, - barcode: pw.Barcode.qrCode(), - drawText: false, + pw.Text( + 'PIN: $pin', + style: pw.TextStyle( + fontSize: 14, + fontWeight: pw.FontWeight.bold, + ), ), - pw.SizedBox(width: 10), - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - mainAxisAlignment: - pw.MainAxisAlignment.spaceAround, - children: [ - pw.Text( - 'PIN: $pin', - style: pw.TextStyle( - fontSize: 14, - fontWeight: pw.FontWeight.bold, - ), - ), - pw.SizedBox(height: 32), - pw.Text( - 'Erstellt am ${DateTime.now().formatForUser()}', - style: const pw.TextStyle(fontSize: 9), - ), - ], + pw.SizedBox(height: 32), + pw.Text( + 'Erstellt am ${DateTime.now().formatDateForUser()}', + style: const pw.TextStyle(fontSize: 9), ), ], ), - if (isStaff) ...[ - pw.SizedBox(height: 10), - pw.Row( - children: [ - pw.Text( - 'Passwort:', - style: pw.TextStyle( - fontSize: 12, - fontWeight: pw.FontWeight.bold, - ), - ), - pw.SizedBox(width: 5), - pw.Text( - password, - style: pw.TextStyle( - fontSize: 12, - fontWeight: pw.FontWeight.bold, - ), - ), - ], - ), - ], ], ), - ), - pw.Expanded(child: pw.Column(children: [])), - ], + if (isStaff) ...[ + pw.SizedBox(height: 10), + pw.Row( + children: [ + pw.Text( + 'Passwort:', + style: pw.TextStyle( + fontSize: 12, + fontWeight: pw.FontWeight.bold, + ), + ), + pw.SizedBox(width: 5), + pw.Text( + password, + style: pw.TextStyle( + fontSize: 12, + fontWeight: pw.FontWeight.bold, + ), + ), + ], + ), + ], + ], + ), ), - ), + pw.Expanded(child: pw.Column(children: [])), + ], + ), + ), ), ); diff --git a/school_data_hub_flutter/lib/features/ogs/widgets/dialogs/ogs_edit_emergency_care_dialog.dart b/school_data_hub_flutter/lib/features/ogs/widgets/dialogs/ogs_edit_emergency_care_dialog.dart new file mode 100644 index 00000000..bd3fd02e --- /dev/null +++ b/school_data_hub_flutter/lib/features/ogs/widgets/dialogs/ogs_edit_emergency_care_dialog.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/theme/styles.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/pupil_mutator.dart'; + +Future ogsEditEmergencyCareDialog( + BuildContext context, + PupilProxy pupil, +) async { + final afterSchoolCare = pupil.afterSchoolCare; + bool? currentValue = afterSchoolCare?.emergencyCare; + + await showDialog( + context: context, + builder: (dialogContext) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: const Text('Notbetreuung bearbeiten'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Ist Notbetreuung aktiviert?', + style: TextStyle(fontSize: 16), + ), + const Gap(20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Radio( + value: true, + groupValue: currentValue, + onChanged: (value) { + setState(() { + currentValue = value; + }); + }, + ), + const Text('Ja'), + const Gap(30), + Radio( + value: false, + groupValue: currentValue, + onChanged: (value) { + setState(() { + currentValue = value; + }); + }, + ), + const Text('Nein'), + const Gap(30), + Radio( + value: null, + groupValue: currentValue, + onChanged: (value) { + setState(() { + currentValue = value; + }); + }, + ), + const Text('Nicht gesetzt'), + ], + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(dialogContext).pop(); + }, + child: const Text('ABBRECHEN'), + ), + ElevatedButton( + style: AppStyles.successButtonStyle, + onPressed: () async { + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + emergencyCare: (value: currentValue), + ); + + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(); + } + }, + child: const Text( + 'SPEICHERN', + style: AppStyles.buttonTextStyle, + ), + ), + ], + ); + }, + ); + }, + ); +} diff --git a/school_data_hub_flutter/lib/features/ogs/widgets/dialogs/ogs_edit_pickup_times_dialog.dart b/school_data_hub_flutter/lib/features/ogs/widgets/dialogs/ogs_edit_pickup_times_dialog.dart new file mode 100644 index 00000000..b79a04cd --- /dev/null +++ b/school_data_hub_flutter/lib/features/ogs/widgets/dialogs/ogs_edit_pickup_times_dialog.dart @@ -0,0 +1,233 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/theme/styles.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/enums.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/pupil_mutator.dart'; + +Future ogsEditPickUpTimesDialog( + BuildContext context, + PupilProxy pupil, +) async { + final afterSchoolCare = pupil.afterSchoolCare; + final currentPickUpTimes = afterSchoolCare?.pickUpTimes; + + // Initialize form state + final formKey = GlobalKey(); + final mondayTimeController = TextEditingController( + text: currentPickUpTimes?.monday?.time ?? '', + ); + final mondayModalityController = TextEditingController( + text: currentPickUpTimes?.monday?.modality ?? '', + ); + final tuesdayTimeController = TextEditingController( + text: currentPickUpTimes?.tuesday?.time ?? '', + ); + final tuesdayModalityController = TextEditingController( + text: currentPickUpTimes?.tuesday?.modality ?? '', + ); + final wednesdayTimeController = TextEditingController( + text: currentPickUpTimes?.wednesday?.time ?? '', + ); + final wednesdayModalityController = TextEditingController( + text: currentPickUpTimes?.wednesday?.modality ?? '', + ); + final thursdayTimeController = TextEditingController( + text: currentPickUpTimes?.thursday?.time ?? '', + ); + final thursdayModalityController = TextEditingController( + text: currentPickUpTimes?.thursday?.modality ?? '', + ); + final fridayTimeController = TextEditingController( + text: currentPickUpTimes?.friday?.time ?? '', + ); + final fridayModalityController = TextEditingController( + text: currentPickUpTimes?.friday?.modality ?? '', + ); + + final result = await showDialog( + context: context, + builder: (dialogContext) { + return AlertDialog( + title: const Text('Abholzeiten bearbeiten'), + content: SingleChildScrollView( + child: Form( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDayRow( + 'Montag', + mondayTimeController, + mondayModalityController, + ), + const Gap(10), + _buildDayRow( + 'Dienstag', + tuesdayTimeController, + tuesdayModalityController, + ), + const Gap(10), + _buildDayRow( + 'Mittwoch', + wednesdayTimeController, + wednesdayModalityController, + ), + const Gap(10), + _buildDayRow( + 'Donnerstag', + thursdayTimeController, + thursdayModalityController, + ), + const Gap(10), + _buildDayRow( + 'Freitag', + fridayTimeController, + fridayModalityController, + ), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(dialogContext).pop(); + }, + child: const Text('ABBRECHEN'), + ), + ElevatedButton( + style: AppStyles.successButtonStyle, + onPressed: () async { + if (!formKey.currentState!.validate()) { + return; + } + + // Update pickup times for each weekday + // The method updates one weekday at a time, so we call it for each weekday + // We always call it for all weekdays to ensure cleared ones are processed + // If time is empty string, it will create PickUpInfo with empty time (effectively clearing it) + + // Monday + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + weekday: (value: AfterSchoolCareWeekday.monday), + time: mondayTimeController.text.trim(), + modality: mondayModalityController.text.trim(), + ); + + // Tuesday + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + weekday: (value: AfterSchoolCareWeekday.tuesday), + time: tuesdayTimeController.text.trim(), + modality: tuesdayModalityController.text.trim(), + ); + + // Wednesday + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + weekday: (value: AfterSchoolCareWeekday.wednesday), + time: wednesdayTimeController.text.trim(), + modality: wednesdayModalityController.text.trim(), + ); + + // Thursday + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + weekday: (value: AfterSchoolCareWeekday.thursday), + time: thursdayTimeController.text.trim(), + modality: thursdayModalityController.text.trim(), + ); + + // Friday + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + weekday: (value: AfterSchoolCareWeekday.friday), + time: fridayTimeController.text.trim(), + modality: fridayModalityController.text.trim(), + ); + + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(); + } + }, + child: const Text('SPEICHERN', style: AppStyles.buttonTextStyle), + ), + ], + ); + }, + ); + + // Dispose controllers after the dialog is fully closed + WidgetsBinding.instance.addPostFrameCallback((_) { + mondayTimeController.dispose(); + mondayModalityController.dispose(); + tuesdayTimeController.dispose(); + tuesdayModalityController.dispose(); + wednesdayTimeController.dispose(); + wednesdayModalityController.dispose(); + thursdayTimeController.dispose(); + thursdayModalityController.dispose(); + fridayTimeController.dispose(); + fridayModalityController.dispose(); + }); + + return result; +} + +Widget _buildDayRow( + String dayName, + TextEditingController timeController, + TextEditingController modalityController, +) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80, + child: Text( + dayName, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + const Gap(10), + Expanded( + flex: 2, + child: TextFormField( + controller: timeController, + decoration: const InputDecoration( + labelText: 'Zeit (z.B. 14:00)', + hintText: '14:00', + border: OutlineInputBorder(), + ), + validator: (value) { + // Allow empty - means no pickup time for that day + if (value == null || value.trim().isEmpty) { + return null; + } + // Basic time format validation + final timeRegex = RegExp(r'^([01]?[0-9]|2[0-3]):[0-5][0-9]$'); + if (!timeRegex.hasMatch(value.trim())) { + return 'Ungültiges Zeitformat (z.B. 14:00)'; + } + return null; + }, + ), + ), + const Gap(10), + Expanded( + flex: 3, + child: TextFormField( + controller: modalityController, + decoration: const InputDecoration( + labelText: 'Modality (optional)', + hintText: 'z.B. Abholung, Bus, etc.', + border: OutlineInputBorder(), + ), + ), + ), + ], + ); +} diff --git a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_details.dart b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_details.dart new file mode 100644 index 00000000..25448954 --- /dev/null +++ b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_details.dart @@ -0,0 +1,376 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_client/school_data_hub_client.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; +import 'package:school_data_hub_flutter/common/widgets/dialogs/long_textfield_dialog.dart'; +import 'package:school_data_hub_flutter/features/ogs/widgets/dialogs/ogs_edit_emergency_care_dialog.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/enums.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/pupil_mutator.dart'; +import 'package:watch_it/watch_it.dart'; + +class OgsDetails extends WatchingWidget { + final PupilProxy pupil; + const OgsDetails({required this.pupil, super.key}); + + @override + Widget build(BuildContext context) { + final afterSchoolCare = watchPropertyValue( + (m) => m.afterSchoolCare, + target: pupil, + ); + + if (afterSchoolCare == null) { + return const Padding( + padding: EdgeInsets.all(10.0), + child: Text( + 'Keine OGS Daten vorhanden', + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ); + } + + final pickUpTimes = afterSchoolCare.pickUpTimes; + + return Column( + children: [ + // Emergency Care + Padding( + padding: const EdgeInsets.all(10.0), + child: InkWell( + onTap: () => ogsEditEmergencyCareDialog(context, pupil), + child: Row( + children: [ + const Text( + 'Notbetreuung:', + style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold), + ), + const Gap(10), + Text( + afterSchoolCare.emergencyCare == null + ? 'Nicht gesetzt' + : (afterSchoolCare.emergencyCare == true ? 'Ja' : 'Nein'), + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + color: afterSchoolCare.emergencyCare == true + ? Colors.orange + : (afterSchoolCare.emergencyCare == null + ? Colors.grey + : Colors.black), + ), + ), + const Gap(10), + const Icon( + Icons.edit, + size: 18, + color: AppColors.backgroundColor, + ), + ], + ), + ), + ), + const Gap(10), + + // Pick Up Times Header + Padding( + padding: const EdgeInsets.only(left: 10, top: 10), + child: const Row( + children: [ + Text( + 'Abholzeiten:', + style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold), + ), + ], + ), + ), + const Gap(5), + // All weekdays with individual edit buttons + _buildPickUpTimeRow( + context, + 'Montag', + pickUpTimes?.monday, + pupil, + AfterSchoolCareWeekday.monday, + ), + _buildPickUpTimeRow( + context, + 'Dienstag', + pickUpTimes?.tuesday, + pupil, + AfterSchoolCareWeekday.tuesday, + ), + _buildPickUpTimeRow( + context, + 'Mittwoch', + pickUpTimes?.wednesday, + pupil, + AfterSchoolCareWeekday.wednesday, + ), + _buildPickUpTimeRow( + context, + 'Donnerstag', + pickUpTimes?.thursday, + pupil, + AfterSchoolCareWeekday.thursday, + ), + _buildPickUpTimeRow( + context, + 'Freitag', + pickUpTimes?.friday, + pupil, + AfterSchoolCareWeekday.friday, + ), + const Gap(10), + + // OGS Info + const Padding( + padding: EdgeInsets.only(left: 10, top: 10), + child: Row( + children: [ + Text( + 'OGS Informationen:', + style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold), + ), + ], + ), + ), + const Gap(5), + Padding( + padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10), + child: InkWell( + onTap: () async { + final String? ogsInfo = await longTextFieldDialog( + title: 'OGS Informationen', + labelText: 'OGS Informationen', + initialValue: afterSchoolCare.afterSchoolCareInfo ?? '', + parentContext: context, + ); + if (ogsInfo == null) return; + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + afterSchoolCareInfo: (value: ogsInfo), + ); + }, + onLongPress: () async { + if (afterSchoolCare.afterSchoolCareInfo == null) return; + final bool? confirm = await confirmationDialog( + context: context, + title: 'OGS Infos löschen', + message: 'OGS Informationen für dieses Kind löschen?', + ); + if (confirm == false || confirm == null) return; + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + afterSchoolCareInfo: (value: null), + ); + }, + child: Text( + afterSchoolCare.afterSchoolCareInfo == null || + (afterSchoolCare.afterSchoolCareInfo?.isEmpty ?? true) + ? 'keine Informationen' + : afterSchoolCare.afterSchoolCareInfo!, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + color: + afterSchoolCare.afterSchoolCareInfo == null || + (afterSchoolCare.afterSchoolCareInfo?.isEmpty ?? true) + ? Colors.grey + : AppColors.backgroundColor, + ), + ), + ), + ), + ], + ); + } + + Widget _buildPickUpTimeRow( + BuildContext context, + String day, + PickUpInfo? pickUpInfo, + PupilProxy pupil, + AfterSchoolCareWeekday weekday, + ) { + return Padding( + padding: const EdgeInsets.only(left: 20, bottom: 5, right: 10), + child: Row( + children: [ + SizedBox( + width: 100, + child: Text( + day, + style: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const Gap(5), + Expanded( + child: Row( + children: [ + InkWell( + onTap: () async { + // Parse current time or use current time as default + TimeOfDay initialTime = TimeOfDay.now(); + final currentTime = pickUpInfo?.time; + if (currentTime != null && currentTime.isNotEmpty) { + final parts = currentTime.split(':'); + if (parts.length == 2) { + final hour = int.tryParse(parts[0]); + final minute = int.tryParse(parts[1]); + if (hour != null && minute != null) { + initialTime = TimeOfDay(hour: hour, minute: minute); + } + } + } + + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: initialTime, + ); + + if (pickedTime != null) { + // Convert TimeOfDay to HH:mm format + final timeString = + '${pickedTime.hour.toString().padLeft(2, '0')}:${pickedTime.minute.toString().padLeft(2, '0')}'; + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + weekday: (value: weekday), + time: timeString, + modality: pickUpInfo?.modality ?? '', + ); + } + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (pickUpInfo?.time == null || + pickUpInfo!.time.isEmpty) ...[ + const Icon( + Icons.access_time, + size: 18, + color: AppColors.backgroundColor, + ), + const Gap(5), + ], + Text( + pickUpInfo?.time ?? 'Nicht gesetzt', + style: TextStyle( + fontSize: + pickUpInfo?.time != null && + pickUpInfo!.time.isNotEmpty + ? 18.0 + : 16.0, + fontWeight: FontWeight.bold, + color: + pickUpInfo?.time != null && + pickUpInfo!.time.isNotEmpty + ? AppColors.backgroundColor + : Colors.grey[600], + fontStyle: + pickUpInfo?.time != null && + pickUpInfo!.time.isNotEmpty + ? FontStyle.normal + : FontStyle.italic, + ), + ), + if (pickUpInfo?.time != null && + pickUpInfo!.time.isNotEmpty) ...[ + const Gap(5), + const Text('Uhr', style: TextStyle(fontSize: 16.0)), + ], + ], + ), + ), + const Gap(5), + Expanded( + child: _buildModalityDropdown( + context, + pickUpInfo?.modality, + pupil, + weekday, + pickUpInfo?.time ?? '', + ), + ), + ], + ), + ), + ], + ), + ); + } + + /// Converts a modality string to the corresponding enum value + AfterSchoolCarePickUpModality _modalityStringToEnum(String? modality) { + if (modality == null || modality.isEmpty) { + return AfterSchoolCarePickUpModality.notSet; + } + // Try to find matching enum by value + for (final enumValue in AfterSchoolCarePickUpModality.values) { + if (enumValue.value == modality) { + return enumValue; + } + } + // If no match found, return notSet + return AfterSchoolCarePickUpModality.notSet; + } + + Widget _buildModalityDropdown( + BuildContext context, + String? currentModality, + PupilProxy pupil, + AfterSchoolCareWeekday weekday, + String time, + ) { + final currentModalityEnum = _modalityStringToEnum(currentModality); + + return DropdownButton( + value: currentModalityEnum, + isDense: true, + isExpanded: true, + underline: Container(), + items: AfterSchoolCarePickUpModality.values.map((modality) { + return DropdownMenuItem( + value: modality, + child: Text( + modality.value, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.0, + fontStyle: modality == AfterSchoolCarePickUpModality.notSet + ? FontStyle.italic + : FontStyle.normal, + color: modality == AfterSchoolCarePickUpModality.notSet + ? Colors.grey + : Colors.black, + ), + ), + ); + }).toList(), + onChanged: (AfterSchoolCarePickUpModality? newModality) async { + if (newModality == null) return; + + // If notSet is selected, we want to clear the modality (set to empty string) + final modalityString = + newModality == AfterSchoolCarePickUpModality.notSet + ? '' + : newModality.value; + + await PupilMutator().updateAfterSchoolCare( + pupilId: pupil.pupilId, + weekday: (value: weekday), + time: time, + modality: modalityString, + ); + }, + ); + } +} diff --git a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_filter_bottom_sheet.dart b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_filter_bottom_sheet.dart index 0ab9e7db..096bf673 100644 --- a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_filter_bottom_sheet.dart +++ b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_filter_bottom_sheet.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; +import 'package:school_data_hub_flutter/common/widgets/themed_filter_chip.dart'; +import 'package:school_data_hub_flutter/features/_attendance/domain/filters/attendance_pupil_filter.dart'; +import 'package:school_data_hub_flutter/features/_attendance/domain/models/enums.dart'; import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/common_pupil_filters.dart'; - import 'package:watch_it/watch_it.dart'; class OgsFilterBottomSheet extends WatchingWidget { @@ -9,13 +11,101 @@ class OgsFilterBottomSheet extends WatchingWidget { @override Widget build(BuildContext context) { - return const Padding( - padding: EdgeInsets.only(left: 20.0, right: 20, top: 8), + final _attendanceFilterLocator = di(); + final Map activeAttendanceFilters = watchValue( + (AttendancePupilFilterManager x) => x.attendancePupilFilterState, + ); + + bool valuePresent = activeAttendanceFilters[AttendancePupilFilter.present]!; + + bool valueNotPresent = + activeAttendanceFilters[AttendancePupilFilter.notPresent]!; + return Padding( + padding: const EdgeInsets.only(left: 20.0, right: 20, top: 8), child: Column( children: [ - FilterHeading(), - CommonPupilFiltersWidget(), - Row(children: [Text('OGS-Filter', style: AppStyles.subtitle)]), + const FilterHeading(), + const CommonPupilFiltersWidget(), + const Row(children: [Text('OGS-Filter', style: AppStyles.subtitle)]), + Wrap( + children: [ + ThemedFilterChip( + label: 'anwesend', + selected: valuePresent, + onSelected: (val) { + // in case present is selected, not present and unexcused should be deselected + + if (val) { + _attendanceFilterLocator.setAttendancePupilFilter( + attendancePupilFilterRecords: [ + ( + attendancePupilFilter: + AttendancePupilFilter.notPresent, + value: false, + ), + ( + attendancePupilFilter: + AttendancePupilFilter.unexcused, + value: false, + ), + ( + attendancePupilFilter: AttendancePupilFilter.present, + value: val, + ), + ], + ); + return; + } + _attendanceFilterLocator.setAttendancePupilFilter( + attendancePupilFilterRecords: [ + ( + attendancePupilFilter: AttendancePupilFilter.present, + value: val, + ), + ], + ); + }, + ), + ThemedFilterChip( + label: 'nicht da', + selected: valueNotPresent, + onSelected: (val) { + // in case not present is selected, present should be deselected + if (val) { + //_valuePresent = false; + _attendanceFilterLocator.setAttendancePupilFilter( + attendancePupilFilterRecords: [ + ( + attendancePupilFilter: + AttendancePupilFilter.notPresent, + value: val, + ), + ( + attendancePupilFilter: AttendancePupilFilter.present, + value: false, + ), + ( + attendancePupilFilter: + AttendancePupilFilter.unexcused, + value: false, + ), + ], + ); + return; + } + + _attendanceFilterLocator.setAttendancePupilFilter( + attendancePupilFilterRecords: [ + ( + attendancePupilFilter: AttendancePupilFilter.notPresent, + value: val, + ), + ], + ); + }, + ), + ], + ), ], ), ); diff --git a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_list_card.dart b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_list_card.dart index 69533c6f..4a8a80fe 100644 --- a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_list_card.dart +++ b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_list_card.dart @@ -1,12 +1,16 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; -import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; -import 'package:school_data_hub_flutter/common/widgets/dialogs/long_textfield_dialog.dart'; -import 'package:school_data_hub_flutter/features/ogs/widgets/dialogs/ogs_pickup_time_dialog.dart'; +import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile.dart'; +import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile_content.dart'; +import 'package:school_data_hub_flutter/features/app_main_navigation/domain/main_menu_bottom_nav_manager.dart'; +import 'package:school_data_hub_flutter/features/ogs/widgets/ogs_details.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/enums.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; -import 'package:school_data_hub_flutter/features/pupil/domain/pupil_mutator.dart'; import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/pupil_profile_page.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_navigation.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/avatar.dart'; +import 'package:school_data_hub_flutter/features/school_calendar/domain/school_calendar_manager.dart'; import 'package:watch_it/watch_it.dart'; class OgsCard extends WatchingWidget { @@ -15,6 +19,10 @@ class OgsCard extends WatchingWidget { @override Widget build(BuildContext context) { final pupil = watch(this.pupil); + final thisDate = watchValue((SchoolCalendarManager x) => x.thisDate); + final weekday = dateTimeToAfterSchoolCareWeekday(thisDate); + final tileController = createOnce(() => CustomExpansionTileController()); + return Card( color: Colors.white, surfaceTintColor: Colors.white, @@ -26,180 +34,107 @@ class OgsCard extends WatchingWidget { top: 4.0, bottom: 4.0, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + child: Column( children: [ - // TODO: Fix avatar component - AvatarWithBadges not available - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(40), - ), - child: const Icon(Icons.person, size: 40), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Gap(15), - Row( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AvatarWithBadges(pupil: pupil, size: 80), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: InkWell( - onTap: () { - // TODO: Fix navigation - MainMenuBottomNavManager not available - // di() - // .setPupilProfileNavPage( - // ProfileNavigation.ogs.value, - // ); - Navigator.of(context).push( - MaterialPageRoute( - builder: - (ctx) => PupilProfilePage( - pupil: pupil, - ), - ), - ); - }, - child: Row( - children: [ - Text( - pupil.firstName, - overflow: TextOverflow.fade, - softWrap: false, - textAlign: TextAlign.left, - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), - const Gap(5), - Text( - pupil.lastName, - overflow: TextOverflow.fade, - softWrap: false, - textAlign: TextAlign.left, - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.normal, - fontSize: 18, - ), - ), - const Gap(5), - ], + const Gap(15), + Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: InkWell( + onTap: () { + di().setPupilProfileNavPage( + ProfileNavigationState.ogs.value, + ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => + PupilProfilePage(pupil: pupil), + ), + ); + }, + child: Row( + children: [ + Text( + pupil.firstName, + overflow: TextOverflow.fade, + softWrap: false, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 18, ), ), - ), - ), - ], - ), - const Gap(25), - const Row(children: [Text('Ogs Infos:'), Gap(5)]), - ], - ), - ), - SizedBox( - width: 100, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - //const Gap(20), - const Text('Abholzeit:'), - Center( - child: InkWell( - onTap: - () => pickUpTimeDialog( - context, - pupil, - pupil.pickUpTime, + const Gap(5), + Text( + pupil.lastName, + overflow: TextOverflow.fade, + softWrap: false, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.normal, + fontSize: 18, + ), ), - child: Text( - pupil.pickUpTime ?? 'keine', - style: const TextStyle( - fontSize: 23, - fontWeight: FontWeight.bold, - color: AppColors.backgroundColor, - ), + const Gap(5), + ], ), ), ), - const Text('Uhr'), - ], - ), + ), + ], ), ], ), - const Gap(5), - Row( + ), + const Gap(20), + InkWell( + onTap: () { + tileController.isExpanded + ? tileController.collapse() + : tileController.expand(); + }, + child: Column( children: [ - Flexible( - child: InkWell( - onTap: () async { - final String? ogsInfo = await longTextFieldDialog( - title: 'OGS Informationen', - labelText: 'OGS Informationen', - initialValue: pupil.ogsInfo ?? '', - parentContext: context, - ); - if (ogsInfo == null) return; - await PupilMutator().updateStringProperty( - pupilId: pupil.internalId, - property: 'afterSchoolCareInfo', - value: ogsInfo, - ); - }, - onLongPress: () async { - if (pupil.ogsInfo == null) return; - final bool? confirm = await confirmationDialog( - context: context, - title: 'OGS Infos löschen', - message: - 'OGS Informationen für dieses Kind löschen?', - ); - if (confirm == false || confirm == null) return; - await PupilMutator().updateStringProperty( - pupilId: pupil.internalId, - property: 'afterSchoolCareInfo', - value: null, - ); - }, - child: Text( - pupil.ogsInfo == null || pupil.ogsInfo!.isEmpty - ? 'keine Infos' - : pupil.ogsInfo!, - overflow: TextOverflow.ellipsis, - softWrap: true, - maxLines: 3, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: AppColors.backgroundColor, - ), + const Gap(20), + const Text('Abholzeit'), + Center( + child: Text( + weekday != null + ? (pupil.pickUpTime(weekday) ?? 'keine') + : 'keine', + style: const TextStyle( + fontSize: 23, + fontWeight: FontWeight.bold, + color: AppColors.backgroundColor, ), ), ), + const Text('Uhr'), ], ), - const Gap(15), - ], - ), + ), + const Gap(20), + ], + ), + CustomExpansionTileContent( + title: null, + tileController: tileController, + widgetList: [OgsDetails(pupil: pupil)], ), ], ), diff --git a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_list_search_bar.dart b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_list_search_bar.dart index 27f8e382..506a656d 100644 --- a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_list_search_bar.dart +++ b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_list_search_bar.dart @@ -61,9 +61,10 @@ class OgsListSearchBar extends StatelessWidget { ), ), const Gap(5), - const FilterButton( + FilterButton( isSearchBar: true, - showBottomSheetFunction: showOgsFilterBottomSheet, + showBottomSheetFunction: () => + showOgsFilterBottomSheet(context), ), ], ), diff --git a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_view_bottom_navbar.dart b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_view_bottom_navbar.dart index e7ade897..d290a83d 100644 --- a/school_data_hub_flutter/lib/features/ogs/widgets/ogs_view_bottom_navbar.dart +++ b/school_data_hub_flutter/lib/features/ogs/widgets/ogs_view_bottom_navbar.dart @@ -24,19 +24,18 @@ class OgsListPageBottomNavBar extends StatelessWidget { const Spacer(), IconButton( tooltip: 'zurück', - icon: const Icon( - Icons.arrow_back, - size: 30, - ), + icon: const Icon(Icons.arrow_back, size: 30), onPressed: () { Navigator.pop(context); }, ), const Gap(30), - const FilterButton( - isSearchBar: true, - showBottomSheetFunction: showOgsFilterBottomSheet), - const Gap(15) + FilterButton( + isSearchBar: true, + showBottomSheetFunction: () => + showOgsFilterBottomSheet(context), + ), + const Gap(15), ], ), ), diff --git a/school_data_hub_flutter/lib/features/ogs/widgets/pupil_ogs_content_list.dart b/school_data_hub_flutter/lib/features/ogs/widgets/pupil_ogs_content_list.dart index 0616e3ec..a982e76f 100644 --- a/school_data_hub_flutter/lib/features/ogs/widgets/pupil_ogs_content_list.dart +++ b/school_data_hub_flutter/lib/features/ogs/widgets/pupil_ogs_content_list.dart @@ -4,19 +4,27 @@ import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/long_textfield_dialog.dart'; import 'package:school_data_hub_flutter/features/ogs/widgets/dialogs/ogs_pickup_time_dialog.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/enums.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/pupil_mutator.dart'; +import 'package:school_data_hub_flutter/features/school_calendar/domain/school_calendar_manager.dart'; +import 'package:watch_it/watch_it.dart'; List pupilOgsContentList(PupilProxy pupil, BuildContext context) { + final schoolCalendarManager = di(); + final thisDate = schoolCalendarManager.thisDate.value; + final weekday = dateTimeToAfterSchoolCareWeekday(thisDate); + final pickUpTime = weekday != null ? pupil.pickUpTime(weekday) : null; + return [ Row( children: [ const Text('Abholzeit:', style: TextStyle(fontSize: 18.0)), const Gap(10), InkWell( - onTap: () => pickUpTimeDialog(context, pupil, pupil.pickUpTime), + onTap: () => pickUpTimeDialog(context, pupil, pickUpTime), child: Text( - pupil.pickUpTime ?? 'keine', + pickUpTime ?? 'keine', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, diff --git a/school_data_hub_flutter/lib/features/pupil/data/pupil_data_api_service.dart b/school_data_hub_flutter/lib/features/pupil/data/pupil_data_api_service.dart index 495aba35..047c573f 100644 --- a/school_data_hub_flutter/lib/features/pupil/data/pupil_data_api_service.dart +++ b/school_data_hub_flutter/lib/features/pupil/data/pupil_data_api_service.dart @@ -25,8 +25,9 @@ class PupilDataApiService { // - update backend pupil database - Future?> updateBackendPupilsDatabase( - {required String filePath}) async { + Future?> updateBackendPupilsDatabase({ + required String filePath, + }) async { final pupils = await ClientHelper.apiCall( call: () => _client.admin.updateBackendPupilDataState(filePath), errorMessage: 'Die Schüler konnten nicht aktualisiert werden', @@ -71,8 +72,12 @@ class PupilDataApiService { String? comment, }) async { final updatedPupil = await ClientHelper.apiCall( - call: () => _client.pupilUpdate - .updateCredit(pupilId, credit, comment, _hubSessionManager.userName!), + call: () => _client.pupilUpdate.updateCredit( + pupilId, + credit, + comment, + _hubSessionManager.userName!, + ), errorMessage: 'Die Schüler konnten nicht aktualisiert werden', ); return updatedPupil; @@ -80,10 +85,11 @@ class PupilDataApiService { //- update pupil one of the pupil properties being a string - Future updateStringProperty( - {required int pupilId, - required String property, - required String? value}) async { + Future updateStringProperty({ + required int pupilId, + required String property, + required String? value, + }) async { final updatedPupil = await ClientHelper.apiCall( call: () => _client.pupilUpdate.updateStringProperty(pupilId, property, value), @@ -92,8 +98,10 @@ class PupilDataApiService { return updatedPupil; } - Future updateSchoolyearHeldBackDate( - {required int pupilId, required ({DateTime? value}) date}) async { + Future updateSchoolyearHeldBackDate({ + required int pupilId, + required ({DateTime? value}) date, + }) async { final updatedPupil = await ClientHelper.apiCall( call: () => _client.pupilUpdate.updateSchoolyearHeldBackDate(pupilId, date), @@ -147,6 +155,19 @@ class PupilDataApiService { ); return updatedPupil; } + + Future updateAfterSchoolCare({ + required int pupilId, + required AfterSchoolCare afterSchoolCare, + }) async { + final updatedPupil = await ClientHelper.apiCall( + call: () => + _client.pupilUpdate.updateAfterSchoolCare(pupilId, afterSchoolCare), + errorMessage: 'Die Schüler konnten nicht aktualisiert werden', + ); + return updatedPupil; + } + //- hub document Future updatePupilDocument({ @@ -155,22 +176,29 @@ class PupilDataApiService { required PupilDocumentType documentType, }) async { final result = await ClientFileUpload.uploadFile( - file, - documentType == PupilDocumentType.avatar - ? ServerStorageFolder.avatars - : ServerStorageFolder.documents); + file, + documentType == PupilDocumentType.avatar + ? ServerStorageFolder.avatars + : ServerStorageFolder.documents, + ); final updatedPupil = await ClientHelper.apiCall( call: () => _client.pupilUpdate.updatePupilDocument( - pupilId, result.path!, _hubSessionManager.userName!, documentType), + pupilId, + result.path!, + _hubSessionManager.userName!, + documentType, + ), errorMessage: 'Das Profilbild konnte nicht aktualisiert werden', ); return updatedPupil; } -//- delete pupil document + //- delete pupil document - Future deletePupilDocument( - {required int pupilId, required PupilDocumentType documentType}) async { + Future deletePupilDocument({ + required int pupilId, + required PupilDocumentType documentType, + }) async { _notificationService.apiRunning(true); final updatedPupil = await ClientHelper.apiCall( call: () => _client.pupil.deletePupilDocument(pupilId, documentType), @@ -179,12 +207,14 @@ class PupilDataApiService { return updatedPupil; } -//- public media auth + //- public media auth Future resetPublicMediaAuth({required int pupilId}) async { final updatedPupil = await ClientHelper.apiCall( - call: () => _client.pupil - .resetPublicMediaAuth(pupilId, _hubSessionManager.userName!), + call: () => _client.pupil.resetPublicMediaAuth( + pupilId, + _hubSessionManager.userName!, + ), errorMessage: 'Die Einwilligung für öffentliche Medien konnte nicht gelöscht werden', ); @@ -192,12 +222,12 @@ class PupilDataApiService { } Future updatePublicMediaAuth( - int pupilId, PublicMediaAuth publicMediaAuth) async { + int pupilId, + PublicMediaAuth publicMediaAuth, + ) async { final updatedPupil = await ClientHelper.apiCall( - call: () => _client.pupilUpdate.updatePublicMediaAuth( - pupilId, - publicMediaAuth, - ), + call: () => + _client.pupilUpdate.updatePublicMediaAuth(pupilId, publicMediaAuth), errorMessage: 'Die Einwilligung für öffentliche Medien konnte nicht aktualisiert werden', ); @@ -214,11 +244,12 @@ class PupilDataApiService { required String comment, }) async { final supportLevel = SupportLevel( - level: supportLevelValue, - comment: comment, - createdAt: createdAt, - createdBy: createdBy, - pupilId: pupilId); + level: supportLevelValue, + comment: comment, + createdAt: createdAt, + createdBy: createdBy, + pupilId: pupilId, + ); final updatedPupil = await ClientHelper.apiCall( call: () => _client.pupilUpdate.updateSupportLevel(supportLevel, pupilId), errorMessage: 'Die Förderebene konnte nicht aktualisiert werden', diff --git a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_filter_enums.dart b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_filter_enums.dart index 4f3a25f6..831bdc28 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_filter_enums.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_filter_enums.dart @@ -1,6 +1,7 @@ enum PupilFilter { - ogs, - notOgs, + afterSchoolCare, + noAfterSchoolCare, + entitledToEmergencyCare, specialInfo, migrationSupport, preSchoolRevision0, @@ -28,11 +29,13 @@ enum PupilFilter { turkishClass, arabicClass, albanianClass, + otherLanguageClass, } Map initialPupilFilterValues = { - PupilFilter.ogs: false, - PupilFilter.notOgs: false, + PupilFilter.afterSchoolCare: false, + PupilFilter.noAfterSchoolCare: false, + PupilFilter.entitledToEmergencyCare: false, PupilFilter.specialInfo: false, PupilFilter.migrationSupport: false, PupilFilter.preSchoolRevision0: false, @@ -58,4 +61,6 @@ Map initialPupilFilterValues = { PupilFilter.muslimReligion: false, PupilFilter.turkishClass: false, PupilFilter.arabicClass: false, + PupilFilter.albanianClass: false, + PupilFilter.otherLanguageClass: false, }; diff --git a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_selector_filters.dart b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_selector_filters.dart index aad822b7..03884d47 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_selector_filters.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_selector_filters.dart @@ -5,7 +5,7 @@ import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy class SchoolGradeFilter extends SelectorFilter { SchoolGradeFilter(SchoolGrade schoolGrade) - : super(name: schoolGrade.name, selector: (proxy) => proxy.schoolGrade); + : super(name: schoolGrade.name, selector: (proxy) => proxy.schoolGrade); @override bool matches(PupilProxy item) { @@ -13,9 +13,26 @@ class SchoolGradeFilter extends SelectorFilter { } } +class ReligionCourseFilter extends SelectorFilter { + ReligionCourseFilter(ReligionCourse religion) + : super( + name: religion.value, + selector: (proxy) => + ReligionCourse.stringToValue[proxy.religion!] ?? + ReligionCourse.none, + ); + + @override + bool matches(PupilProxy item) { + return (selector(item).value == name) && + (item.religionLessonsSince != null && + item.religionLessonsSince != null); + } +} + class GroupFilter extends SelectorFilter { GroupFilter(String group) - : super(name: group, selector: (proxy) => proxy.groupId); + : super(name: group, selector: (proxy) => proxy.groupId); @override bool matches(PupilProxy item) { @@ -25,9 +42,10 @@ class GroupFilter extends SelectorFilter { class GenderFilter extends SelectorFilter { GenderFilter(Gender gender) - : super( - name: gender.value == 'm' ? '♂️' : '♀️', - selector: (proxy) => Gender.stringToValue[proxy.gender]!); + : super( + name: gender.value == 'm' ? '♂️' : '♀️', + selector: (proxy) => Gender.stringToValue[proxy.gender]!, + ); @override bool matches(PupilProxy item) { diff --git a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_text_filter.dart b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_text_filter.dart index faea7573..1f2a9dd4 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_text_filter.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupil_text_filter.dart @@ -5,9 +5,7 @@ import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy final _log = Logger('PupilTextFilter'); class PupilTextFilter extends Filter { - PupilTextFilter({ - required super.name, - }); + PupilTextFilter({required super.name}); String _text = ''; String get text => _text; @@ -20,7 +18,7 @@ class PupilTextFilter extends Filter { return; } toggle(true); - _log.finer('Setting filter text: $text'); + _log.finer('Setting pupil filter text: $text'); notifyListeners(); return; } diff --git a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupils_filter.dart b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupils_filter.dart index ddf21ad9..8cb5f6aa 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupils_filter.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupils_filter.dart @@ -96,6 +96,7 @@ abstract class PupilsFilter implements Listenable { //List get groupFilters; List get schoolGradeFilters; List get genderFilters; + List get religionCourseFilters; PupilTextFilter get textFilter; /// must be called when this object is no longer needed diff --git a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupils_filter_impl.dart b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupils_filter_impl.dart index 8380fd69..9fd1daa8 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/filters/pupils_filter_impl.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/filters/pupils_filter_impl.dart @@ -19,17 +19,20 @@ import 'package:school_data_hub_flutter/features/pupil/domain/pupil_identity_man import 'package:school_data_hub_flutter/features/pupil/domain/pupil_manager.dart'; import 'package:watch_it/watch_it.dart'; -final _pupilIdentityManager = di(); -final _learningSupportFilterManager = di(); - -final _schooldayEventFilterManager = di(); -final _pupilFilterManager = di(); -final _attendancePupilFilterManager = di(); -final _filtersStateManager = di(); - final _log = Logger('PupilsFilterImplementation'); class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { + // Lazy getters to avoid accessing dependencies during construction + PupilIdentityManager get _pupilIdentityManager => di(); + LearningSupportFilterManager get _learningSupportFilterManager => + di(); + SchooldayEventFilterManager get _schooldayEventFilterManager => + di(); + PupilFilterManager get _pupilFilterManager => di(); + AttendancePupilFilterManager get _attendancePupilFilterManager => + di(); + FiltersStateManager get _filtersStateManager => di(); + PupilsFilterImplementation( PupilManager pupilsManager, // { @@ -84,6 +87,7 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { ...schoolGradeFilters, ..._groupFilters, ...genderFilters, + ...religionCourseFilters, _textFilter, ]; @@ -102,12 +106,15 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // reset the filtered pupils to all pupils _filteredPupils.value = _pupilsManager.allPupils; - _filteredPupilIds.value = - _pupilsManager.allPupils.map((e) => e.pupilId).toList(); + _filteredPupilIds.value = _pupilsManager.allPupils + .map((e) => e.pupilId) + .toList(); sortPupils(); _filtersStateManager.setFilterState( - filterState: FilterState.pupil, value: false); + filterState: FilterState.pupil, + value: false, + ); } // updates the filtered pupils with current filters @@ -120,15 +127,18 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // checks if any not yet migrated filters are active - final bool specificFiltersOn = _pupilFilterManager - .pupilFilterState.value.values - .any((x) => x == true) || + final bool specificFiltersOn = + _pupilFilterManager.pupilFilterState.value.values.any( + (x) => x == true, + ) || _schooldayEventFilterManager.schooldayEventsFilterState.value.values .any((x) => x == true) || - _learningSupportFilterManager.supportLevelFilterState.value.values - .any((x) => x == true) || - _learningSupportFilterManager.supportAreaFilterState.value.values - .any((x) => x == true) || + _learningSupportFilterManager.supportLevelFilterState.value.values.any( + (x) => x == true, + ) || + _learningSupportFilterManager.supportAreaFilterState.value.values.any( + (x) => x == true, + ) || _filtersStateManager.getFilterState(FilterState.attendance); // If no filters are active, just sort @@ -146,20 +156,28 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { bool isAnyGroupFilterActive = groupFilters.any((filter) => filter.isActive); - bool isAnySchoolGradeFilterActive = - schoolGradeFilters.any((filter) => filter.isActive); + bool isAnySchoolGradeFilterActive = schoolGradeFilters.any( + (filter) => filter.isActive, + ); + + bool isAnyGenderFilterActive = genderFilters.any( + (filter) => filter.isActive, + ); - bool isAnyGenderFilterActive = - genderFilters.any((filter) => filter.isActive); + bool isAnyReligionCourseFilterActive = religionCourseFilters.any( + (filter) => filter.isActive, + ); bool isTextFilterActive = _textFilter.isActive; for (final pupil in allPupils) { // matches if no group filter is active or if the group matches the pupil's group - bool isMatchedByGroupFilter = !isAnyGroupFilterActive || - groupFilters - .any((filter) => filter.isActive && filter.matches(pupil)); + bool isMatchedByGroupFilter = + !isAnyGroupFilterActive || + groupFilters.any( + (filter) => filter.isActive && filter.matches(pupil), + ); // if the pupil is not matched by any group filter, skip it @@ -169,9 +187,11 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { } // matches if no school grade filter is active or if the school grade matches the pupil's grade - bool isMatchedBySchoolGradeFilter = !isAnySchoolGradeFilterActive || - schoolGradeFilters - .any((filter) => filter.isActive && filter.matches(pupil)); + bool isMatchedBySchoolGradeFilter = + !isAnySchoolGradeFilterActive || + schoolGradeFilters.any( + (filter) => filter.isActive && filter.matches(pupil), + ); // if the pupil is not matched by any school grade filter, skip itl @@ -180,14 +200,28 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { continue; } - bool isMatchedByGenderFilter = !isAnyGenderFilterActive || - genderFilters - .any((filter) => filter.isActive && filter.matches(pupil)); + bool isMatchedByGenderFilter = + !isAnyGenderFilterActive || + genderFilters.any( + (filter) => filter.isActive && filter.matches(pupil), + ); if (!isMatchedByGenderFilter) { if (filtersOn == false) filtersOn = true; continue; } + + bool isMatchedByReligionCourseFilter = + !isAnyReligionCourseFilterActive || + religionCourseFilters.any( + (filter) => filter.isActive && filter.matches(pupil), + ); + + if (!isMatchedByReligionCourseFilter) { + if (filtersOn == false) filtersOn = true; + continue; + } + // if the pupil is not matched by the text filter, skip it if (_textFilter.isActive && !_textFilter.matches(pupil)) { @@ -198,8 +232,9 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // if attendance filters are on, pass the pupil through the attendance filters if (di().getFilterState(FilterState.attendance)) { - if (!_attendancePupilFilterManager - .isMatchedByAttendanceFilters(pupil)) { + if (!_attendancePupilFilterManager.isMatchedByAttendanceFilters( + pupil, + )) { if (filtersOn == false) filtersOn = true; continue; } @@ -207,8 +242,9 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // schoolday event filters - if (di() - .getFilterState(FilterState.schooldayEvent)) { + if (di().getFilterState( + FilterState.schooldayEvent, + )) { if (!di() .pupilIdsWithFilteredSchooldayEvents .value @@ -220,9 +256,11 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // after school care filters - if (_pupilFilterManager.pupilFilterState.value[PupilFilter.ogs]! && + if (_pupilFilterManager.pupilFilterState.value[PupilFilter + .afterSchoolCare]! && pupil.afterSchoolCare == null || - _pupilFilterManager.pupilFilterState.value[PupilFilter.notOgs]! && + _pupilFilterManager.pupilFilterState.value[PupilFilter + .noAfterSchoolCare]! && pupil.afterSchoolCare != null) { if (filtersOn == false) filtersOn = true; continue; @@ -231,8 +269,9 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // support level filters if (di().supportLevelFiltersActive) { - if (!di() - .matchSupportLevelFilters(pupil)) { + if (!di().matchSupportLevelFilters( + pupil, + )) { if (filtersOn == false) filtersOn = true; continue; } @@ -241,8 +280,9 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // support area filters if (di().supportAreaFiltersActive) { - if (!di() - .matchSupportAreaFilters(pupil)) { + if (!di().matchSupportAreaFilters( + pupil, + )) { if (filtersOn == false) filtersOn = true; continue; } @@ -250,8 +290,8 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // language support filters - if (_pupilFilterManager - .pupilFilterState.value[PupilFilter.migrationSupport]! && + if (_pupilFilterManager.pupilFilterState.value[PupilFilter + .migrationSupport]! && !PupilHelper.hasLanguageSupport(pupil.migrationSupportEnds)) { if (filtersOn == false) filtersOn = true; continue; @@ -272,6 +312,9 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { // Set modified filter value @override void setSortMode(PupilSortMode sortMode) { + if (sortMode == _sortMode.value) { + return; + } _sortMode.value = sortMode; refreshs(); notifyListeners(); @@ -293,38 +336,58 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { filteredPupils.sort((b, a) => a.creditEarned.compareTo(b.creditEarned)); case PupilSortMode.sortBySchooldayEvents: - filteredPupils.sort((a, b) => SchoolDayEventHelper.schooldayEventSum(b) - .compareTo(SchoolDayEventHelper.schooldayEventSum(a))); + filteredPupils.sort( + (a, b) => SchoolDayEventHelper.schooldayEventSum( + b, + ).compareTo(SchoolDayEventHelper.schooldayEventSum(a)), + ); case PupilSortMode.sortByLastSchooldayEvent: - filteredPupils.sort((a, b) => - SchoolDayEventHelper.getPupilLastSchooldayEventDate(b).compareTo( - SchoolDayEventHelper.getPupilLastSchooldayEventDate(a))); + filteredPupils.sort( + (a, b) => SchoolDayEventHelper.getPupilLastSchooldayEventDate( + b, + ).compareTo(SchoolDayEventHelper.getPupilLastSchooldayEventDate(a)), + ); case PupilSortMode.sortByLastNonProcessedSchooldayEvent: filteredPupils.sort( - SchoolDayEventHelper.comparePupilsByLastNonProcessedSchooldayEvent); + SchoolDayEventHelper.comparePupilsByLastNonProcessedSchooldayEvent, + ); case PupilSortMode.sortByMissedUnexcused: - filteredPupils.sort((a, b) => - AttendanceHelper.missedclassUnexcusedSum(b) - .compareTo(AttendanceHelper.missedclassUnexcusedSum(a))); + filteredPupils.sort( + (a, b) => AttendanceHelper.missedclassUnexcusedSum( + b, + ).compareTo(AttendanceHelper.missedclassUnexcusedSum(a)), + ); case PupilSortMode.sortByMissedExcused: - filteredPupils.sort((a, b) => AttendanceHelper.missedclassExcusedSum(b) - .compareTo(AttendanceHelper.missedclassExcusedSum(a))); + filteredPupils.sort( + (a, b) => AttendanceHelper.missedclassExcusedSum( + b, + ).compareTo(AttendanceHelper.missedclassExcusedSum(a)), + ); case PupilSortMode.sortByLate: - filteredPupils.sort((a, b) => AttendanceHelper.lateUnexcusedSum(b) - .compareTo(AttendanceHelper.lateUnexcusedSum(a))); + filteredPupils.sort( + (a, b) => AttendanceHelper.lateUnexcusedSum( + b, + ).compareTo(AttendanceHelper.lateUnexcusedSum(a)), + ); case PupilSortMode.sortByContacted: - filteredPupils.sort((a, b) => AttendanceHelper.contactedSum(b) - .compareTo(AttendanceHelper.contactedSum(a))); + filteredPupils.sort( + (a, b) => AttendanceHelper.contactedSum( + b, + ).compareTo(AttendanceHelper.contactedSum(a)), + ); case PupilSortMode.sortByGoneHome: - filteredPupils.sort((a, b) => AttendanceHelper.goneHomeSum(b) - .compareTo(AttendanceHelper.goneHomeSum(a))); + filteredPupils.sort( + (a, b) => AttendanceHelper.goneHomeSum( + b, + ).compareTo(AttendanceHelper.goneHomeSum(a)), + ); } _filteredPupils.value = filteredPupils; _filteredPupilIds.value = filteredPupils.map((e) => e.pupilId).toList(); @@ -334,8 +397,10 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { @override void setTextFilter(String? text, {bool refresh = true}) { if (text != null && text.isNotEmpty) { - di() - .setFilterState(filterState: FilterState.pupil, value: true); + di().setFilterState( + filterState: FilterState.pupil, + value: true, + ); } notifyListeners(); _textFilter.setFilterText(text ?? ''); @@ -346,19 +411,20 @@ class PupilsFilterImplementation with ChangeNotifier implements PupilsFilter { @override // List get groupFilters => PupilProxy.groupFilters; - @override List get schoolGradeFilters => PupilProxy.schoolGradeFilters; @override List get genderFilters => PupilProxy.genderFilters; + @override + List get religionCourseFilters => PupilProxy.religionCourseFilters; + @override void populateGroupFilters(List groupIds) { - final groupFilters = groupIds - .map((groupId) => GroupFilter(groupId)) - .toList() - ..sort((a, b) => a.name.compareTo(b.name)); + final groupFilters = + groupIds.map((groupId) => GroupFilter(groupId)).toList() + ..sort((a, b) => a.name.compareTo(b.name)); _groupFilters.clear(); _groupFilters.addAll(groupFilters); diff --git a/school_data_hub_flutter/lib/features/pupil/domain/models/enums.dart b/school_data_hub_flutter/lib/features/pupil/domain/models/enums.dart index e20d5a4b..d0d32a42 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/models/enums.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/models/enums.dart @@ -25,7 +25,7 @@ Map initialSortModeValues = { PupilSortMode.sortByGoneHome: false, PupilSortMode.sortBySchooldayEvents: false, PupilSortMode.sortByLastSchooldayEvent: false, - PupilSortMode.sortByLastNonProcessedSchooldayEvent: false + PupilSortMode.sortByLastNonProcessedSchooldayEvent: false, }; // enum SchoolGrade { @@ -47,15 +47,64 @@ Map initialSortModeValues = { // const SchoolGrade(this.value); // } +enum ReligionCourse { + islam('isl.'), + catholic('kath.'), + none('andere'); + + static const stringToValue = { + 'isl.': ReligionCourse.islam, + 'röm.kath.': ReligionCourse.catholic, + 'andere': ReligionCourse.none, + }; + + final String value; + const ReligionCourse(this.value); +} + enum Gender { male('m'), female('w'); - static const stringToValue = { - 'm': Gender.male, - 'w': Gender.female, - }; + static const stringToValue = {'m': Gender.male, 'w': Gender.female}; final String value; const Gender(this.value); } + +enum AfterSchoolCareWeekday { monday, tuesday, wednesday, thursday, friday } + +/// Converts a DateTime to AfterSchoolCareWeekday +/// Handles UTC dates by converting to local time first +/// Returns null if the date is not a weekday (Saturday or Sunday) +AfterSchoolCareWeekday? dateTimeToAfterSchoolCareWeekday(DateTime date) { + // Convert UTC to local time to get the correct weekday for the user's timezone + final localDate = date.isUtc ? date.toLocal() : date; + + // DateTime.weekday: Monday=1, Tuesday=2, ..., Friday=5, Saturday=6, Sunday=7 + switch (localDate.weekday) { + case 1: + return AfterSchoolCareWeekday.monday; + case 2: + return AfterSchoolCareWeekday.tuesday; + case 3: + return AfterSchoolCareWeekday.wednesday; + case 4: + return AfterSchoolCareWeekday.thursday; + case 5: + return AfterSchoolCareWeekday.friday; + default: + return null; // Saturday or Sunday + } +} + +enum AfterSchoolCarePickUpModality { + alone('allein'), + anyRelative('Verwandte'), + anyOther('Bekannte'), + exception('Besondere Regelung'), + notSet('bitte wählen'); + + final String value; + const AfterSchoolCarePickUpModality(this.value); +} diff --git a/school_data_hub_flutter/lib/features/pupil/domain/models/extensions.dart b/school_data_hub_flutter/lib/features/pupil/domain/models/extensions.dart index 0534f6d8..4c73a98f 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/models/extensions.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/models/extensions.dart @@ -1,5 +1,5 @@ import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; extension PupilIdentityExtensions on PupilIdentity { bool isEqualTo(PupilIdentity other) { @@ -17,21 +17,24 @@ extension PupilIdentityExtensions on PupilIdentity { (migrationSupportEnds == other.migrationSupportEnds || (migrationSupportEnds != null && other.migrationSupportEnds != null && - migrationSupportEnds! - .isSameDate(other.migrationSupportEnds!))) && + migrationSupportEnds!.isSameDate( + other.migrationSupportEnds!, + ))) && pupilSince.isSameDate(other.pupilSince) && afterSchoolCare == other.afterSchoolCare && religion == other.religion && (religionLessonsSince == other.religionLessonsSince || (religionLessonsSince != null && other.religionLessonsSince != null && - religionLessonsSince! - .isSameDate(other.religionLessonsSince!))) && + religionLessonsSince!.isSameDate( + other.religionLessonsSince!, + ))) && (familyLanguageLessonsSince == other.familyLanguageLessonsSince || (familyLanguageLessonsSince != null && other.familyLanguageLessonsSince != null && - familyLanguageLessonsSince! - .isSameDate(other.familyLanguageLessonsSince!))) && + familyLanguageLessonsSince!.isSameDate( + other.familyLanguageLessonsSince!, + ))) && (leavingDate == other.leavingDate || (leavingDate != null && other.leavingDate != null && diff --git a/school_data_hub_flutter/lib/features/pupil/domain/models/pupil_proxy.dart b/school_data_hub_flutter/lib/features/pupil/domain/models/pupil_proxy.dart index 1bdc34f8..73bc9c51 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/models/pupil_proxy.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/models/pupil_proxy.dart @@ -31,6 +31,12 @@ class PupilProxy with ChangeNotifier { GenderFilter(Gender.female), ]; + static List religionCourseFilters = [ + ReligionCourseFilter(ReligionCourse.islam), + ReligionCourseFilter(ReligionCourse.catholic), + ReligionCourseFilter(ReligionCourse.none), + ]; + late PupilData _pupilData; PupilIdentity _pupilIdentity; @@ -56,6 +62,8 @@ class PupilProxy with ChangeNotifier { } } + //- PupilIdentity GETTERS + String get firstName => _pupilIdentity.firstName; String get lastName => _pupilIdentity.lastName; @@ -87,6 +95,9 @@ class PupilProxy with ChangeNotifier { DateTime? get familyLanguageLessonsSince => _pupilIdentity.familyLanguageLessonsSince; + DateTime? get religionLessonsSince => _pupilIdentity.religionLessonsSince; + + String? get religion => _pupilIdentity.religion; //- PUPIL DATA GETTERS int get pupilId => _pupilData.id!; @@ -131,16 +142,22 @@ class PupilProxy with ChangeNotifier { // TODO URGENT: Remove these after migrating all OGS code to use proper afterSchoolCare model bool get ogs => afterSchoolCare != null; - String? get pickUpTime { - // For now, return a simplified representation - // This would need to be updated based on business logic for which day to show + String? pickUpTime(AfterSchoolCareWeekday weekday) { final pickUpTimes = afterSchoolCare?.pickUpTimes; - if (pickUpTimes?.monday != null) return pickUpTimes!.monday!.time; - if (pickUpTimes?.tuesday != null) return pickUpTimes!.tuesday!.time; - if (pickUpTimes?.wednesday != null) return pickUpTimes!.wednesday!.time; - if (pickUpTimes?.thursday != null) return pickUpTimes!.thursday!.time; - if (pickUpTimes?.friday != null) return pickUpTimes!.friday!.time; - return null; + if (pickUpTimes == null) return null; + + switch (weekday) { + case AfterSchoolCareWeekday.monday: + return pickUpTimes.monday?.time; + case AfterSchoolCareWeekday.tuesday: + return pickUpTimes.tuesday?.time; + case AfterSchoolCareWeekday.wednesday: + return pickUpTimes.wednesday?.time; + case AfterSchoolCareWeekday.thursday: + return pickUpTimes.thursday?.time; + case AfterSchoolCareWeekday.friday: + return pickUpTimes.friday?.time; + } } String? get ogsInfo => afterSchoolCare?.afterSchoolCareInfo; diff --git a/school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_helper_functions.dart b/school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_helper_functions.dart index fd093c15..55618311 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_helper_functions.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_helper_functions.dart @@ -11,31 +11,71 @@ import 'package:watch_it/watch_it.dart'; final _log = Logger('PupilIdentityHelper'); class PupilIdentityHelper { + //- TIMEZONE CONVERSION HELPERS + + /// Converts a DateTime string from Berlin timezone (UTC+1) to UTC. + /// Returns null if the dateString is null or cannot be parsed. + static DateTime? _convertBerlinDateStringToUtc(String? dateString) { + if (dateString == null) return null; + final berlinDate = DateTime.tryParse(dateString); + if (berlinDate == null) return null; + return berlinDate.subtract(Duration(hours: 1)); + } + //- LOCAL STORAGE HELPERS - static Future> readPupilIdentitiesFromStorage( - {required String secureStorageKey}) async { + static Future> readPupilIdentitiesFromStorage({ + required String secureStorageKey, + }) async { final pupilsJson = await HubSecureStorage().getString(secureStorageKey); if (pupilsJson == null) return {}; final Map decodedJson = jsonDecode(pupilsJson); - return Map.fromEntries(decodedJson.entries.map( - (entry) { + return Map.fromEntries( + decodedJson.entries.map((entry) { final int pupilId = int.parse(entry.key); - final PupilIdentity pupilIdentity = PupilIdentity.fromJson(entry.value); + // Clone the JSON map and convert DateTime strings from Berlin to UTC + final Map jsonData = Map.from( + entry.value as Map, + ); + + // Convert all DateTime fields from Berlin timezone to UTC + final dateTimeFields = [ + 'birthday', + 'migrationSupportEnds', + 'pupilSince', + 'religionLessonsSince', + 'familyLanguageLessonsSince', + 'leavingDate', + ]; + + for (final field in dateTimeFields) { + if (jsonData[field] != null) { + final convertedDate = _convertBerlinDateStringToUtc( + jsonData[field] as String?, + ); + if (convertedDate != null) { + jsonData[field] = convertedDate.toIso8601String(); + } + } + } + + final PupilIdentity pupilIdentity = PupilIdentity.fromJson(jsonData); return MapEntry(pupilId, pupilIdentity); - }, - )); + }), + ); } static Future deletePupilIdentitiesForEnv( - String secureStorageKey) async { + String secureStorageKey, + ) async { await HubSecureStorage().remove(secureStorageKey); _log.warning( - 'Pupil identities for environment $secureStorageKey have been deleted.'); + 'Pupil identities for environment $secureStorageKey have been deleted.', + ); di().clearPupilIdentities(); di().clearFilteredPupils(); @@ -46,7 +86,8 @@ class PupilIdentityHelper { //- OBJECT HELPERS static PupilIdentity decodePupilIdentityFromStringList( - List pupilIdentityStringItems) { + List pupilIdentityStringItems, + ) { final SchoolGrade schoolgrade; switch (pupilIdentityStringItems[5]) { case 'E1': @@ -91,24 +132,36 @@ class PupilIdentityHelper { family: pupilIdentityStringItems[10] == '' ? null : pupilIdentityStringItems[10], - birthday: DateTime.tryParse(pupilIdentityStringItems[11])!, + birthday: DateTime.tryParse( + pupilIdentityStringItems[11], + )!.subtract(Duration(hours: 1)), migrationSupportEnds: pupilIdentityStringItems[12] == '' ? null - : DateTime.tryParse(pupilIdentityStringItems[12])!, - pupilSince: DateTime.tryParse(pupilIdentityStringItems[13])!, + : DateTime.tryParse( + pupilIdentityStringItems[12], + )!.subtract(Duration(hours: 1)), + pupilSince: DateTime.tryParse( + pupilIdentityStringItems[13], + )!.subtract(Duration(hours: 1)), afterSchoolCare: pupilIdentityStringItems[14] != '' ? true : false, religion: pupilIdentityStringItems[15] == '' ? null : pupilIdentityStringItems[15], religionLessonsSince: pupilIdentityStringItems[16] == '' ? null - : DateTime.tryParse(pupilIdentityStringItems[16])!, + : DateTime.tryParse( + pupilIdentityStringItems[16], + )!.subtract(Duration(hours: 1)), familyLanguageLessonsSince: pupilIdentityStringItems[18] == '' ? null - : DateTime.tryParse(pupilIdentityStringItems[18])!, + : DateTime.tryParse( + pupilIdentityStringItems[18], + )!.subtract(Duration(hours: 1)), leavingDate: pupilIdentityStringItems[18] == '' ? null - : DateTime.tryParse(pupilIdentityStringItems[19]), + : DateTime.tryParse( + pupilIdentityStringItems[19], + )?.subtract(Duration(hours: 1)), ); return newPupilIdentity; } diff --git a/school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_manager.dart b/school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_manager.dart index 1bc4ccd1..c771844e 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_manager.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_manager.dart @@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; import 'package:school_data_hub_flutter/app_utils/custom_encrypter.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/app_utils/secure_storage.dart'; import 'package:school_data_hub_flutter/common/data/file_upload_service.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; @@ -137,8 +137,8 @@ class PupilIdentityManager { // We check now if the identities are outdated - final lastIdentitiesUpdate = - await PupilDataApiService().fetchLastIdentitiesUpdate(); + final lastIdentitiesUpdate = await PupilDataApiService() + .fetchLastIdentitiesUpdate(); if (lastIdentitiesUpdate != null && activeEnv.lastIdentitiesUpdate != null) { if (lastIdentitiesUpdate.isAfter(activeEnv.lastIdentitiesUpdate!)) { @@ -209,8 +209,9 @@ class PupilIdentityManager { await writePupilIdentitiesToStorage(); if (updateGroupFilters) { - final availableGroups = - _pupilIdentities.values.map((e) => e.group).toSet(); + final availableGroups = _pupilIdentities.values + .map((e) => e.group) + .toSet(); _groups.value = availableGroups; di().populateGroupFilters(availableGroups.toList()); } @@ -257,8 +258,9 @@ class PupilIdentityManager { importedPupilIdentityList.add(pupilIdentity); - final bool ogsStatus = - pupilIdentityValues[14] == 'OFFGANZ' ? true : false; + final bool ogsStatus = pupilIdentityValues[14] == 'OFFGANZ' + ? true + : false; final idAndOgsStatus = '${int.parse(pupilIdentityValues[0])},$ogsStatus'; @@ -310,10 +312,9 @@ class PupilIdentityManager { ); await ClientHelper.apiCall( - call: - () => di().pupilIdentity.updateLastPupilIdentitiesUpdate( - DateTime.now().toUtc(), - ), + call: () => di().pupilIdentity.updateLastPupilIdentitiesUpdate( + DateTime.now().toUtc(), + ), errorMessage: 'Die letzte Aktualisierung konnte nicht gespeichert werden', ); @@ -340,21 +341,19 @@ class PupilIdentityManager { } _notificationService.showSnackBar( NotificationType.success, - 'Letzte Aktualisierung: ${lastUpdate.formatForJson()}', + 'Letzte Aktualisierung: ${lastUpdate.formatDateForUser()}', ); } Future generatePupilIdentitiesQrData(List internalIds) async { String qrString = ''; for (int internalId in internalIds) { - PupilIdentity pupilIdentity = - _pupilIdentities.values - .where((element) => element.id == internalId) - .single; - final migrationSupportEnds = - pupilIdentity.migrationSupportEnds != null - ? pupilIdentity.migrationSupportEnds!.formatForJson() - : ''; + PupilIdentity pupilIdentity = _pupilIdentities.values + .where((element) => element.id == internalId) + .single; + final migrationSupportEnds = pupilIdentity.migrationSupportEnds != null + ? pupilIdentity.migrationSupportEnds!.formatDateForJson() + : ''; // We need final specialNeeds = pupilIdentity.specialNeeds ?? ''; final family = pupilIdentity.family ?? ''; @@ -372,13 +371,13 @@ class PupilIdentityManager { pupilIdentity.gender, pupilIdentity.language, family, - pupilIdentity.birthday.formatForJson(), + pupilIdentity.birthday.formatDateForJson(), migrationSupportEnds, - pupilIdentity.pupilSince.formatForJson(), + pupilIdentity.pupilSince.formatDateForJson(), pupilIdentity.afterSchoolCare, pupilIdentity.religion ?? '', - pupilIdentity.religionLessonsSince?.formatForJson() ?? '', - pupilIdentity.leavingDate?.formatForJson() ?? '', + pupilIdentity.religionLessonsSince?.formatDateForJson() ?? '', + pupilIdentity.leavingDate?.formatDateForJson() ?? '', ].join(',') + ',\n'; qrString = qrString + pupilIdentityString; @@ -469,8 +468,9 @@ class PupilIdentityManager { break; case 'confirmed': // Check if confirmation is for a specific user - final targetUser = - event.value.isNotEmpty ? event.value : null; + final targetUser = event.value.isNotEmpty + ? event.value + : null; if (targetUser == null) { // Legacy: no targeting, proceed for any receiver if (onRequestConfirmed != null) { diff --git a/school_data_hub_flutter/lib/features/pupil/domain/pupil_manager.dart b/school_data_hub_flutter/lib/features/pupil/domain/pupil_manager.dart index 0d29cd19..f392896f 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/pupil_manager.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/pupil_manager.dart @@ -183,7 +183,9 @@ class PupilManager extends ChangeNotifier { final pupilsToFetch = di().availablePupilIds; if (pupilsToFetch.isEmpty) { - _log.info('No pupil identities to fetch data from the backend'); + _log.info( + 'No pupil identities available, canot fetch pupils without ids', + ); return; } await fetchPupilsByInternalId(pupilsToFetch); @@ -221,13 +223,12 @@ class PupilManager extends ChangeNotifier { } // check if we did not get a pupil response for some ids // if so, we will delete the personal data for those ids later - final List outdatedPupilIdentitiesIds = - pupilInternalIds - .where( - (element) => - !fetchedPupils.any((pupil) => pupil.internalId == element), - ) - .toList(); + final List outdatedPupilIdentitiesIds = pupilInternalIds + .where( + (element) => + !fetchedPupils.any((pupil) => pupil.internalId == element), + ) + .toList(); // now we match the pupils from the response with their personal data @@ -361,10 +362,12 @@ class PupilManager extends ChangeNotifier { lentBy: lentBy ?? pupilBookLending.lentBy, status: status != null ? status.value : pupilBookLending.status, score: score != null ? score.value : pupilBookLending.score, - returnedAt: - returnedAt != null ? returnedAt.value : pupilBookLending.returnedAt, - receivedBy: - receivedBy != null ? receivedBy.value : pupilBookLending.receivedBy, + returnedAt: returnedAt != null + ? returnedAt.value + : pupilBookLending.returnedAt, + receivedBy: receivedBy != null + ? receivedBy.value + : pupilBookLending.receivedBy, ); final pupil = await _pupilBookApiService.updatePupilBookLending( bookLending: updatedBookLending, diff --git a/school_data_hub_flutter/lib/features/pupil/domain/pupil_mutator.dart b/school_data_hub_flutter/lib/features/pupil/domain/pupil_mutator.dart index 4a270aa7..14cb416e 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/pupil_mutator.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/pupil_mutator.dart @@ -5,6 +5,7 @@ import 'package:school_data_hub_client/school_data_hub_client.dart'; import 'package:school_data_hub_flutter/app_utils/custom_encrypter.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; import 'package:school_data_hub_flutter/features/pupil/data/pupil_data_api_service.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/enums.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/pupil_manager.dart'; import 'package:watch_it/watch_it.dart'; @@ -339,4 +340,75 @@ class PupilMutator { _pupilManager.updatePupilProxyWithPupilData(updatedPupil); } + + Future updateAfterSchoolCare({ + required int pupilId, + ({String? value})? afterSchoolCareInfo, + ({bool? value})? emergencyCare, + ({AfterSchoolCareWeekday? value})? weekday, + String? time, + String? modality, + }) async { + // first we get the current after school care value or create a new one if it is null + final afterSchoolCarePupilValue = + di().getPupilByPupilId(pupilId)!.afterSchoolCare ?? + AfterSchoolCare(); + AfterSchoolCarePickUpTimes? pickUpTimes = + afterSchoolCarePupilValue.pickUpTimes ?? AfterSchoolCarePickUpTimes(); + + if (weekday != null) { + switch (weekday.value!) { + case AfterSchoolCareWeekday.monday: + pickUpTimes.monday = PickUpInfo( + modality: modality ?? pickUpTimes.monday?.modality ?? '', + time: time ?? pickUpTimes.monday?.time ?? '', + ); + break; + case AfterSchoolCareWeekday.tuesday: + pickUpTimes.tuesday = PickUpInfo( + modality: modality ?? pickUpTimes.tuesday?.modality ?? '', + time: time ?? pickUpTimes.tuesday?.time ?? '', + ); + break; + case AfterSchoolCareWeekday.wednesday: + pickUpTimes.wednesday = PickUpInfo( + modality: modality ?? pickUpTimes.wednesday?.modality ?? '', + time: time ?? pickUpTimes.wednesday?.time ?? '', + ); + break; + case AfterSchoolCareWeekday.thursday: + pickUpTimes.thursday = PickUpInfo( + modality: modality ?? pickUpTimes.thursday?.modality ?? '', + time: time ?? pickUpTimes.thursday?.time ?? '', + ); + break; + case AfterSchoolCareWeekday.friday: + pickUpTimes.friday = PickUpInfo( + modality: modality ?? pickUpTimes.friday?.modality ?? '', + time: time ?? pickUpTimes.friday?.time ?? '', + ); + break; + } + } + + final afterSchoolCareToUpdate = afterSchoolCarePupilValue.copyWith( + afterSchoolCareInfo: afterSchoolCareInfo != null + ? afterSchoolCareInfo.value + : afterSchoolCarePupilValue.afterSchoolCareInfo, + emergencyCare: emergencyCare != null + ? emergencyCare.value + : afterSchoolCarePupilValue.emergencyCare, + pickUpTimes: pickUpTimes, + ); + + final PupilData? updatedPupil = await _pupilDataApiService + .updateAfterSchoolCare( + pupilId: pupilId, + afterSchoolCare: afterSchoolCareToUpdate, + ); + if (updatedPupil == null) { + return; + } + _pupilManager.updatePupilProxyWithPupilData(updatedPupil); + } } diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/birthdays_page.dart b/school_data_hub_flutter/lib/features/pupil/presentation/birthdays_page.dart index 9c6ea351..5eb35cda 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/birthdays_page.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/birthdays_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; @@ -15,8 +15,8 @@ class BirthdaysView extends StatelessWidget { @override Widget build(BuildContext context) { final Set seenBirthdays = {}; - final List pupils = - di().getPupilsWithBirthdaySinceDate(selectedDate); + final List pupils = di() + .getPupilsWithBirthdaySinceDate(selectedDate); return Scaffold( backgroundColor: AppColors.canvasColor, @@ -27,58 +27,71 @@ class BirthdaysView extends StatelessWidget { title: const Text('Geburtstage', style: AppStyles.appBarTextStyle), ), body: Center( - child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Expanded( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 800), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Geburtstage seit dem ${selectedDate.formatForUser()}', + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Geburtstage seit dem ${selectedDate.formatDateForUser()}', style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 20)), - ListView.builder( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ListView.builder( padding: const EdgeInsets.only(top: 10, bottom: 10), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: pupils.length, itemBuilder: (context, int index) { PupilProxy listedPupil = pupils[index]; - final bool isBirthdayPrinted = - seenBirthdays.contains(DateTime( + final bool isBirthdayPrinted = seenBirthdays + .contains( + DateTime( DateTime.now().year, listedPupil.birthday.month, - listedPupil.birthday.day)); + listedPupil.birthday.day, + ), + ); if (!isBirthdayPrinted) { - seenBirthdays.add(DateTime( + seenBirthdays.add( + DateTime( DateTime.now().year, listedPupil.birthday.month, - listedPupil.birthday.day)); + listedPupil.birthday.day, + ), + ); } return Column( children: [ !isBirthdayPrinted ? Padding( padding: const EdgeInsets.symmetric( - vertical: 5.0), + vertical: 5.0, + ), child: Row( children: [ const Gap(5), Text( - '${DateTime(DateTime.now().year, listedPupil.birthday.month, listedPupil.birthday.day).asWeekdayName(context)}, ${listedPupil.birthday.formatForUser()}', + '${DateTime(DateTime.now().year, listedPupil.birthday.month, listedPupil.birthday.day).asWeekdayName(context)}, ${listedPupil.birthday.formatDateForUser()}', style: const TextStyle( - fontWeight: FontWeight.bold, - color: AppColors - .backgroundColor, - fontSize: 18), - ) + fontWeight: FontWeight.bold, + color: + AppColors.backgroundColor, + fontSize: 18, + ), + ), ], ), ) @@ -99,7 +112,9 @@ class BirthdaysView extends StatelessWidget { child: Row( children: [ AvatarWithBadges( - pupil: listedPupil, size: 80), + pupil: listedPupil, + size: 80, + ), const Gap(10), Column( crossAxisAlignment: @@ -108,14 +123,15 @@ class BirthdaysView extends StatelessWidget { Text( listedPupil.firstName, style: const TextStyle( - fontWeight: - FontWeight.bold, - fontSize: 18), + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), Text( listedPupil.lastName, style: const TextStyle( - fontSize: 18), + fontSize: 18, + ), ), ], ), @@ -127,19 +143,19 @@ class BirthdaysView extends StatelessWidget { Text( listedPupil.age.toString(), style: const TextStyle( - fontWeight: - FontWeight.bold, - color: Colors.black, - fontSize: 24), + fontWeight: FontWeight.bold, + color: Colors.black, + fontSize: 24, + ), ), const Gap(5), const Text( 'Jahre alt', style: TextStyle( - fontWeight: - FontWeight.bold, - color: Colors.black, - fontSize: 18), + fontWeight: FontWeight.bold, + color: Colors.black, + fontSize: 18, + ), ), ], ), @@ -149,18 +165,20 @@ class BirthdaysView extends StatelessWidget { ), ), ), - const Gap(5) + const Gap(5), ], ); - }), - ], + }, + ), + ], + ), ), ), ), ), ), - ) - ]), + ], + ), ), floatingActionButton: FloatingActionButton( onPressed: () { diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/family_language_lessons_list_page.dart b/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/family_language_lessons_list_page.dart new file mode 100644 index 00000000..46fbc3c8 --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/family_language_lessons_list_page.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/theme/styles.dart'; +import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_sliver_list.dart'; +import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_sliver_search_app_bar.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/pupil_manager.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_card.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_list_page_bottom_navbar.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_list_search_bar.dart'; +import 'package:watch_it/watch_it.dart'; + +final _filterStateManager = di(); + +final _pupilManager = di(); + +List familyLanguageLessonsFilter(List pupils) { + List filteredPupils = []; + bool filtersOn = false; + for (PupilProxy pupil in pupils) { + if (pupil.familyLanguageLessonsSince == null) { + filtersOn = true; + continue; + } + + filteredPupils.add(pupil); + } + if (filtersOn) { + _filterStateManager.setFilterState( + filterState: FilterState.pupil, + value: true, + ); + } + return filteredPupils; +} + +void _onPop(bool didPop, dynamic result) { + _filterStateManager.resetFilters(); +} + +class FamilyLanguageLessonsListPage extends WatchingWidget { + const FamilyLanguageLessonsListPage({super.key}); + + @override + Widget build(BuildContext context) { + List filteredPupils = watchValue( + (PupilsFilter x) => x.filteredPupils, + ); + List pupils = familyLanguageLessonsFilter(filteredPupils); + onDispose(() { + _filterStateManager.resetFilters(); + }); + return PopScope( + onPopInvokedWithResult: (didPop, result) => _onPop(didPop, result), + child: Scaffold( + backgroundColor: AppColors.canvasColor, + appBar: AppBar( + centerTitle: true, + backgroundColor: AppColors.backgroundColor, + title: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.translate, size: 25, color: Colors.white), + Gap(10), + Text( + 'Herkunftssprachlicher U.', + style: AppStyles.appBarTextStyle, + ), + ], + ), + automaticallyImplyLeading: false, + ), + body: RefreshIndicator( + onRefresh: () async => _pupilManager.updatePupilList(pupils), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 700), + child: CustomScrollView( + slivers: [ + const SliverGap(5), + GenericSliverSearchAppBar( + height: 110, + title: FamilyLanguageLessonsListSearchBar(pupils: pupils), + ), + GenericSliverListWithEmptyListCheck( + items: pupils, + itemBuilder: (_, pupil) => FamilyLanguageLessonsCard(pupil), + ), + ], + ), + ), + ), + ), + bottomNavigationBar: FamilyLanguageLessonsListPageBottomNavBar(), + ), + ); + } +} diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_card.dart b/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_card.dart new file mode 100644 index 00000000..a2cd0128 --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_card.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; +import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; +import 'package:school_data_hub_flutter/features/app_main_navigation/domain/main_menu_bottom_nav_manager.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/pupil_profile_page.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/avatar.dart'; +import 'package:watch_it/watch_it.dart'; + +final _mainMenuBottomNavManager = di(); +final _filterStateManager = di(); + +class FamilyLanguageLessonsCard extends WatchingWidget { + final PupilProxy pupil; + const FamilyLanguageLessonsCard(this.pupil, {super.key}); + @override + Widget build(BuildContext context) { + return Card( + color: Colors.white, + surfaceTintColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + elevation: 1.0, + margin: const EdgeInsets.only( + left: 4.0, + right: 4.0, + top: 4.0, + bottom: 4.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AvatarWithBadges(pupil: pupil, size: 80), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(15), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: InkWell( + onTap: () { + _filterStateManager.resetFilters(); + _mainMenuBottomNavManager + .setPupilProfileNavPage(0); + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => + PupilProfilePage(pupil: pupil), + ), + ); + }, + child: Row( + children: [ + Text( + pupil.firstName, + overflow: TextOverflow.fade, + softWrap: false, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + const Gap(5), + Text( + pupil.lastName, + overflow: TextOverflow.fade, + softWrap: false, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.normal, + fontSize: 18, + ), + ), + const Gap(5), + ], + ), + ), + ), + ), + ], + ), + const Gap(5), + Row( + children: [ + const Text('Herkunftssprache:'), + const Gap(10), + Text( + pupil.language.isNotEmpty + ? pupil.language + : 'keine Angabe', + overflow: TextOverflow.ellipsis, + softWrap: true, + maxLines: 3, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), + ], + ), + ), + ], + ), + const Gap(5), + Row( + children: [ + Text('Angemeldet am:'), + const Gap(10), + Text( + pupil.familyLanguageLessonsSince != null + ? pupil.familyLanguageLessonsSince! + .formatDateForUser() + : 'keine Angabe', + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_list_page_bottom_navbar.dart b/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_list_page_bottom_navbar.dart new file mode 100644 index 00000000..1d24202c --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_list_page_bottom_navbar.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/widgets/bottom_nav_bar_layouts.dart'; +import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_filter_bottom_sheet.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/common_pupil_filters.dart'; +import 'package:watch_it/watch_it.dart'; + +final _pupilsFilter = di(); + +class FamilyLanguageLessonsListPageBottomNavBar extends WatchingWidget { + const FamilyLanguageLessonsListPageBottomNavBar({super.key}); + + @override + Widget build(BuildContext context) { + final filtersOn = watchValue((FiltersStateManager x) => x.filtersActive); + return BottomNavBarLayout( + bottomNavBar: BottomAppBar( + height: 60, + padding: const EdgeInsets.all(10), + shape: null, + color: AppColors.backgroundColor, + child: IconTheme( + data: IconThemeData(color: Theme.of(context).colorScheme.onPrimary), + child: Row( + children: [ + const Spacer(), + IconButton( + tooltip: 'zurück', + icon: const Icon( + Icons.arrow_back, + size: 30, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + const Gap(30), + InkWell( + onTap: () => + showGenericFilterBottomSheet(context: context, filterList: [ + const CommonPupilFiltersWidget(), + ]), + onLongPress: () => _pupilsFilter.resetFilters(), + child: Icon( + Icons.filter_list, + color: filtersOn ? Colors.deepOrange : Colors.white, + size: 30, + ), + ), + const Gap(15) + ], + ), + ), + ), + ); + } +} + diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_list_search_bar.dart b/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_list_search_bar.dart new file mode 100644 index 00000000..2bca77bf --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/family_language_lessons_page/widgets/family_language_lessons_list_search_bar.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/domain/models/enums.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/widgets/filter_button.dart'; +import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_filter_bottom_sheet.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/common_pupil_filters.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/pupil_search_text_field.dart'; +import 'package:watch_it/watch_it.dart'; + +final _pupilsFilter = di(); + +class FamilyLanguageLessonsListSearchBar extends WatchingWidget { + final List pupils; + const FamilyLanguageLessonsListSearchBar({required this.pupils, super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.canvasColor, + borderRadius: BorderRadius.circular(5.0), + ), + child: Column( + children: [ + const Gap(5), + Padding( + padding: const EdgeInsets.only(left: 10.0, right: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.people_alt_rounded, + color: AppColors.backgroundColor, + ), + const Gap(5), + Text( + pupils.length.toString(), + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Row( + children: [ + Expanded( + child: PupilSearchTextField( + searchType: SearchType.pupil, + hintText: 'Schüler/in suchen', + refreshFunction: _pupilsFilter.refreshs)), + FilterButton( + isSearchBar: true, + showBottomSheetFunction: () => showGenericFilterBottomSheet( + context: context, filterList: [ + const CommonPupilFiltersWidget(), + ]), + ) + ], + ), + ), + ], + ), + ); + } +} + diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/after_school_care_content/pupil_ogs_content.dart b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/after_school_care_content/pupil_ogs_content.dart index 019e3b63..20d0afa4 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/after_school_care_content/pupil_ogs_content.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/after_school_care_content/pupil_ogs_content.dart @@ -1,165 +1,44 @@ -// import 'package:flutter/material.dart'; -// import 'package:gap/gap.dart'; -// import 'package:schuldaten_hub/common/services/locator.dart'; -// import 'package:schuldaten_hub/common/theme/app_colors.dart'; -// import 'package:schuldaten_hub/common/theme/paddings.dart'; -// import 'package:schuldaten_hub/common/widgets/dialogs/confirmation_dialog.dart'; -// import 'package:schuldaten_hub/common/widgets/dialogs/long_textfield_dialog.dart'; -// import 'package:schuldaten_hub/features/ogs/widgets/dialogs/ogs_pickup_time_dialog.dart'; -// import 'package:schuldaten_hub/features/pupil/domain/models/pupil_proxy.dart'; -// import 'package:schuldaten_hub/features/pupil/domain/pupil_helper_functions.dart'; -// import 'package:schuldaten_hub/features/pupil/domain/pupil_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/theme/paddings.dart'; +import 'package:school_data_hub_flutter/features/ogs/widgets/ogs_details.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; -// class PupilOgsContent extends StatelessWidget { -// final PupilProxy pupil; -// const PupilOgsContent({required this.pupil, super.key}); +class PupilOgsContent extends StatelessWidget { + final PupilProxy pupil; + const PupilOgsContent({required this.pupil, super.key}); -// @override -// Widget build(BuildContext context) { -// return Card( -// color: AppColors.pupilProfileCardColor, -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(10), -// ), -// child: Padding( -// padding: AppPaddings.pupilProfileCardPadding, -// child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ -// const Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ -// Icon( -// Icons.lightbulb, -// color: AppColors.accentColor, -// size: 24, -// ), -// Gap(5), -// Text('OGS-Informationen', -// style: TextStyle( -// fontSize: 24, -// fontWeight: FontWeight.bold, -// color: AppColors.backgroundColor, -// )) -// ]), -// const Gap(15), -// Row( -// children: [ -// const Text('Notbetreuungsberechtigt: '), -// const Gap(5), -// InkWell( -// onTap: () async { -// final bool? confirm = await confirmationDialog( -// context: context, -// title: 'Notbetreuungsberechtigung ändern', -// message: -// 'Notbetreuungsberechtigung für dieses Kind ändern?'); -// if (confirm == false || confirm == null) return; -// await locator().patchOnePupilProperty( -// pupilId: pupil.internalId, -// jsonKey: 'emergency_care', -// value: pupil.emergencyCare == true ? 'false' : 'true'); -// }, -// child: Text( -// pupil.emergencyCare == true ? 'Ja' : 'Nein', -// style: const TextStyle( -// fontWeight: FontWeight.bold, -// fontSize: 16, -// color: AppColors.backgroundColor, -// ), -// ), -// ), -// ], -// ), -// if (pupil.ogs == false) -// const Row( -// children: [ -// Gap(25), -// Text( -// 'Nicht angemeldet.', -// style: TextStyle( -// fontWeight: FontWeight.bold, -// fontSize: 16, -// color: AppColors.backgroundColor, -// ), -// ), -// ], -// ) -// else -// Column( -// children: [ -// Row( -// children: [ -// const Gap(25), -// Flexible( -// child: InkWell( -// onTap: () async { -// final String? ogsInfo = await longTextFieldDialog( -// title: 'OGS Informationen', -// labelText: 'OGS Informationen', -// textinField: pupil.ogsInfo ?? '', -// parentContext: context); -// if (ogsInfo == null) return; -// await locator().patchOnePupilProperty( -// pupilId: pupil.internalId, -// jsonKey: 'ogs_info', -// value: ogsInfo); -// }, -// onLongPress: () async { -// if (pupil.ogsInfo == null) return; -// final bool? confirm = await confirmationDialog( -// context: context, -// title: 'OGS Infos löschen', -// message: -// 'OGS Informationen für dieses Kind löschen?'); -// if (confirm == false || confirm == null) return; -// await locator().patchOnePupilProperty( -// pupilId: pupil.internalId, -// jsonKey: 'ogs_info', -// value: null); -// }, -// child: Text( -// pupil.ogsInfo == null || pupil.ogsInfo!.isEmpty -// ? 'keine Infos' -// : pupil.ogsInfo!, -// overflow: TextOverflow.ellipsis, -// softWrap: true, -// maxLines: 3, -// style: const TextStyle( -// fontWeight: FontWeight.bold, -// fontSize: 16, -// color: AppColors.backgroundColor, -// ), -// ), -// ), -// ), -// ], -// ), -// const Gap(15), -// Row( -// children: [ -// const Gap(25), -// Row( -// children: [ -// const Text('Abholzeit:'), -// const Gap(5), -// InkWell( -// onTap: () => pickUpTimeDialog( -// context, pupil, pupil.pickUpTime), -// child: Text( -// pickUpValue(pupil.pickUpTime), -// style: const TextStyle( -// fontSize: 23, -// fontWeight: FontWeight.bold, -// color: AppColors.backgroundColor), -// ), -// ), -// const Gap(5), -// const Text('Uhr'), -// ], -// ), -// ], -// ), -// ], -// ), -// ]), -// ), -// ); -// } -// } + @override + Widget build(BuildContext context) { + return Card( + color: AppColors.pupilProfileCardColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: Padding( + padding: AppPaddings.pupilProfileCardPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(Icons.lightbulb, color: AppColors.accentColor, size: 24), + Gap(5), + Text( + 'OGS-Informationen', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.backgroundColor, + ), + ), + ], + ), + const Gap(15), + OgsDetails(pupil: pupil), + ], + ), + ), + ); + } +} diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/attendance_content/pupil_profile_attendance_content.dart b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/attendance_content/pupil_profile_attendance_content.dart index 96378aa3..7c972c29 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/attendance_content/pupil_profile_attendance_content.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/attendance_content/pupil_profile_attendance_content.dart @@ -21,10 +21,10 @@ class PupilProfileAttendanceContent extends StatelessWidget { Widget build(BuildContext context) { final missedHoursForActualReport = AttendanceHelper.missedHoursforSemesterOrSchoolyear(pupil); - List missedSchooldays = - _attendanceManager - .getPupilMissedSchooldaysProxy(pupil.pupilId) - .missedSchooldays; + + List missedSchooldays = _attendanceManager + .getPupilMissedSchooldaysProxy(pupil.pupilId) + .missedSchooldays; // sort by missedDay missedSchooldays.sort( (b, a) => a.schoolday!.schoolday.compareTo(b.schoolday!.schoolday), @@ -49,8 +49,8 @@ class PupilProfileAttendanceContent extends StatelessWidget { onTap: () { Navigator.of(context).push( MaterialPageRoute( - builder: - (ctx) => const MissedSchooldayesPupilListPage(), + builder: (ctx) => + const MissedSchooldayesPupilListPage(), ), ); }, diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/communication_values.dart b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/communication_values.dart index bbc4a923..ec91a79f 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/communication_values.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/communication_values.dart @@ -1,16 +1,13 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/pupil_helper_functions.dart'; class CommunicationValues extends StatelessWidget { final CommunicationSkills? communicationSkills; - const CommunicationValues({ - required this.communicationSkills, - super.key, - }); + const CommunicationValues({required this.communicationSkills, super.key}); @override Widget build(BuildContext context) { @@ -25,9 +22,12 @@ class CommunicationValues extends StatelessWidget { const Gap(10), Text( PupilHelper.communicationPredicate( - communicationSkills?.understanding), + communicationSkills?.understanding, + ), style: const TextStyle( - fontSize: 16, color: AppColors.interactiveColor), + fontSize: 16, + color: AppColors.interactiveColor, + ), ), ], ), @@ -39,9 +39,12 @@ class CommunicationValues extends StatelessWidget { const Gap(10), Text( PupilHelper.communicationPredicate( - communicationSkills?.speaking), + communicationSkills?.speaking, + ), style: const TextStyle( - fontSize: 16, color: AppColors.interactiveColor), + fontSize: 16, + color: AppColors.interactiveColor, + ), ), ], ), @@ -53,9 +56,12 @@ class CommunicationValues extends StatelessWidget { const Gap(10), Text( PupilHelper.communicationPredicate( - communicationSkills?.reading), + communicationSkills?.reading, + ), style: const TextStyle( - fontSize: 16, color: AppColors.interactiveColor), + fontSize: 16, + color: AppColors.interactiveColor, + ), ), ], ), @@ -65,8 +71,9 @@ class CommunicationValues extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.end, children: [ Text( - 'Erstellt von ${communicationSkills!.createdBy} am ${communicationSkills!.createdAt.formatForUser()}', - style: const TextStyle(fontSize: 12.0, color: Colors.grey)), + 'Erstellt von ${communicationSkills!.createdBy} am ${communicationSkills!.createdAt.formatDateForUser()}', + style: const TextStyle(fontSize: 12.0, color: Colors.grey), + ), ], ), const Gap(5), diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/pupil_profile_communication_content.dart b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/pupil_profile_communication_content.dart index 0203f02b..5fcbc4c1 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/pupil_profile_communication_content.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/pupil_profile_communication_content.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/information_dialog.dart'; @@ -82,19 +82,17 @@ class PupilProfileCommunicationContent extends WatchingWidget { _buildInfoRow( icon: Icons.support_outlined, label: 'Erstförderung', - value: - pupil.migrationSupportEnds != null - ? 'bis : ${pupil.migrationSupportEnds!.formatForUser()}' - : 'keine', + value: pupil.migrationSupportEnds != null + ? 'bis : ${pupil.migrationSupportEnds!.formatDateForUser()}' + : 'keine', ), const Gap(8), _buildInfoRow( icon: Icons.support_outlined, label: 'HKU', - value: - pupil.familyLanguageLessonsSince != null - ? 'seit ${pupil.familyLanguageLessonsSince!.formatForUser()}' - : 'nein', + value: pupil.familyLanguageLessonsSince != null + ? 'seit ${pupil.familyLanguageLessonsSince!.formatDateForUser()}' + : 'nein', ), ], ), @@ -109,12 +107,11 @@ class PupilProfileCommunicationContent extends WatchingWidget { _buildCommunicationRow( label: 'Kind', communicationSkills: communicationPupil, - onTap: - () => languageDialog( - context, - pupil, - CommunicationSubject.pupil, - ), + onTap: () => languageDialog( + context, + pupil, + CommunicationSubject.pupil, + ), onLongPress: () async { if (_hubSessionManager.isAdmin == false) { informationDialog( @@ -141,12 +138,11 @@ class PupilProfileCommunicationContent extends WatchingWidget { _buildCommunicationRow( label: 'Mutter / TutorIn 1', communicationSkills: tutorInfo?.communicationTutor1, - onTap: - () => languageDialog( - context, - pupil, - CommunicationSubject.tutor1, - ), + onTap: () => languageDialog( + context, + pupil, + CommunicationSubject.tutor1, + ), onLongPress: () async { final isAdmin = _hubSessionManager.isAdmin; if (!isAdmin) { @@ -176,12 +172,11 @@ class PupilProfileCommunicationContent extends WatchingWidget { _buildCommunicationRow( label: 'Vater / TutorIn 2', communicationSkills: tutorInfo?.communicationTutor2, - onTap: - () => languageDialog( - context, - pupil, - CommunicationSubject.tutor2, - ), + onTap: () => languageDialog( + context, + pupil, + CommunicationSubject.tutor2, + ), onLongPress: () async { final isAdmin = _hubSessionManager.isAdmin; if (!isAdmin) { @@ -200,12 +195,11 @@ class PupilProfileCommunicationContent extends WatchingWidget { if (success == true) { PupilMutator().updateTutorInfo( pupilId: pupil.pupilId, - tutorInfo: - tutorInfo != null - ? tutorInfo.copyWith(communicationTutor2: null) - : TutorInfo( - createdBy: _hubSessionManager.userName!, - ), + tutorInfo: tutorInfo != null + ? tutorInfo.copyWith(communicationTutor2: null) + : TutorInfo( + createdBy: _hubSessionManager.userName!, + ), ); } }, @@ -306,10 +300,9 @@ class PupilProfileCommunicationContent extends WatchingWidget { horizontal: 12, ), decoration: BoxDecoration( - color: - onTap != null - ? AppColors.interactiveColor.withValues(alpha: 0.1) - : Colors.transparent, + color: onTap != null + ? AppColors.interactiveColor.withValues(alpha: 0.1) + : Colors.transparent, borderRadius: BorderRadius.circular(8), ), child: Text( @@ -317,10 +310,9 @@ class PupilProfileCommunicationContent extends WatchingWidget { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: - onTap != null - ? AppColors.interactiveColor - : Colors.black87, + color: onTap != null + ? AppColors.interactiveColor + : Colors.black87, ), ), ), @@ -393,19 +385,18 @@ class PupilProfileCommunicationContent extends WatchingWidget { width: 1, ), ), - child: - communicationSkills == null - ? const Text( - 'kein Eintrag - tippen zum Hinzufügen', - style: TextStyle( - fontSize: 14, - fontStyle: FontStyle.italic, - color: AppColors.interactiveColor, - ), - ) - : CommunicationValues( - communicationSkills: communicationSkills, + child: communicationSkills == null + ? const Text( + 'kein Eintrag - tippen zum Hinzufügen', + style: TextStyle( + fontSize: 14, + fontStyle: FontStyle.italic, + color: AppColors.interactiveColor, ), + ) + : CommunicationValues( + communicationSkills: communicationSkills, + ), ), ), ], diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/infos_content/pupil_profile_infos_content.dart b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/infos_content/pupil_profile_infos_content.dart index 27237f2b..276bdc29 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/infos_content/pupil_profile_infos_content.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/infos_content/pupil_profile_infos_content.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/app_utils/pdf_viewer_page.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; @@ -108,16 +108,14 @@ class PupilProfileInfosContent extends WatchingWidget { width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: - pupil.specialInformation != null - ? AppColors.backgroundColor.withValues(alpha: 0.05) - : AppColors.interactiveColor.withValues(alpha: 0.05), + color: pupil.specialInformation != null + ? AppColors.backgroundColor.withValues(alpha: 0.05) + : AppColors.interactiveColor.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), border: Border.all( - color: - pupil.specialInformation != null - ? AppColors.backgroundColor.withValues(alpha: 0.2) - : AppColors.interactiveColor.withValues(alpha: 0.2), + color: pupil.specialInformation != null + ? AppColors.backgroundColor.withValues(alpha: 0.2) + : AppColors.interactiveColor.withValues(alpha: 0.2), width: 1, ), ), @@ -126,18 +124,15 @@ class PupilProfileInfosContent extends WatchingWidget { 'Tippen Sie hier, um besondere Informationen hinzuzufügen', style: TextStyle( fontSize: 16, - fontWeight: - pupil.specialInformation != null - ? FontWeight.w500 - : FontWeight.normal, - color: - pupil.specialInformation != null - ? AppColors.backgroundColor - : AppColors.interactiveColor, - fontStyle: - pupil.specialInformation == null - ? FontStyle.italic - : FontStyle.normal, + fontWeight: pupil.specialInformation != null + ? FontWeight.w500 + : FontWeight.normal, + color: pupil.specialInformation != null + ? AppColors.backgroundColor + : AppColors.interactiveColor, + fontStyle: pupil.specialInformation == null + ? FontStyle.italic + : FontStyle.normal, ), ), ), @@ -159,13 +154,13 @@ class PupilProfileInfosContent extends WatchingWidget { _buildInfoRow( icon: Icons.cake_outlined, label: 'Geburtsdatum', - value: pupil.birthday.formatForUser(), + value: pupil.birthday.formatDateForUser(), ), const Gap(8), // Reduced from 12 to 8 _buildInfoRow( icon: Icons.school_outlined, label: 'Aufnahmedatum', - value: pupil.pupilSince.formatForUser(), + value: pupil.pupilSince.formatDateForUser(), ), ], ), @@ -180,10 +175,9 @@ class PupilProfileInfosContent extends WatchingWidget { _buildContactRow( icon: Icons.person_outline, label: 'Schüler/in Kontakt', - value: - pupil.contact?.isNotEmpty == true - ? pupil.contact! - : 'keine Angabe', + value: pupil.contact?.isNotEmpty == true + ? pupil.contact! + : 'keine Angabe', onTap: () { if (pupil.contact != null && pupil.contact!.isNotEmpty) { MatrixPolicyHelper.launchMatrixUrl( @@ -207,71 +201,66 @@ class PupilProfileInfosContent extends WatchingWidget { value: contact, ); }, - actionButton: - pupil.contact == null || pupil.contact!.isEmpty - ? IconButton( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: - (ctx) => NewMatrixUserPage( - pupil: pupil, - matrixId: - MatrixPolicyHelper.generateMatrixId( - isParent: false, - ), - displayName: - '${pupil.firstName} ${pupil.lastName.substring(0, 1).toUpperCase()}. (${pupil.group})', - ), + actionButton: pupil.contact == null || pupil.contact!.isEmpty + ? IconButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => NewMatrixUserPage( + pupil: pupil, + matrixId: MatrixPolicyHelper.generateMatrixId( + isParent: false, + ), + displayName: + '${pupil.firstName} ${pupil.lastName.substring(0, 1).toUpperCase()}. (${pupil.group})', ), + ), + ); + }, + icon: const Icon( + Icons.add_circle_outline, + size: 24, + color: AppColors.interactiveColor, + ), + ) + : IconButton( + onPressed: () async { + final confirmation = await confirmationDialog( + context: context, + title: 'Passwort zurücksetzen', + message: + 'Möchten Sie das Passwort wirklich zurücksetzen?', + ); + if (confirmation != true) return; + if (context.mounted) { + final logOutDevices = await logoutDevicesDialog( + context, ); - }, - icon: const Icon( - Icons.add_circle_outline, - size: 24, - color: AppColors.interactiveColor, - ), - ) - : IconButton( - onPressed: () async { - final confirmation = await confirmationDialog( - context: context, - title: 'Passwort zurücksetzen', - message: - 'Möchten Sie das Passwort wirklich zurücksetzen?', - ); - if (confirmation != true) return; - if (context.mounted) { - final logOutDevices = await logoutDevicesDialog( - context, - ); - if (logOutDevices == null) return; - final file = await _matrixPolicyManager.users - .resetPasswordAndPrintCredentialsFile( - user: - MatrixUserHelper.usersFromUserIds([ - pupil.contact!, - ]).first, - logoutDevices: logOutDevices, - isStaff: false, - ); - if (file != null && context.mounted) { - Navigator.of(context).push( - MaterialPageRoute( - builder: - (context) => - PdfViewerPage(pdfFile: file), - ), + if (logOutDevices == null) return; + final file = await _matrixPolicyManager.users + .resetPasswordAndPrintCredentialsFile( + user: MatrixUserHelper.usersFromUserIds([ + pupil.contact!, + ]).first, + logoutDevices: logOutDevices, + isStaff: false, ); - } + if (file != null && context.mounted) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + PdfViewerPage(pdfFile: file), + ), + ); } - }, - icon: const Icon( - Icons.qr_code_2_rounded, - size: 24, - color: AppColors.backgroundColor, - ), + } + }, + icon: const Icon( + Icons.qr_code_2_rounded, + size: 24, + color: AppColors.backgroundColor, ), + ), ), const Gap(10), // Reduced from 16 to 10 _buildContactRow( @@ -300,93 +289,85 @@ class PupilProfileInfosContent extends WatchingWidget { if (parentsContact == null) return; await PupilMutator().updateTutorInfo( pupilId: pupil.pupilId, - tutorInfo: - pupil.tutorInfo != null - ? pupil.tutorInfo!.copyWith( - parentsContact: parentsContact, - ) - : TutorInfo( - parentsContact: parentsContact, - createdBy: _hubSessionManager.userName!, - ), + tutorInfo: pupil.tutorInfo != null + ? pupil.tutorInfo!.copyWith( + parentsContact: parentsContact, + ) + : TutorInfo( + parentsContact: parentsContact, + createdBy: _hubSessionManager.userName!, + ), ); }, - actionButton: - pupil.tutorInfo?.parentsContact == null - ? IconButton( - onPressed: () { - String? pupilSiblingsGroups; - if (pupil.family != null) { - pupilSiblingsGroups = - [ - ..._pupilManager.getSiblings(pupil), - pupil, - ].map((e) => e.group).toList().join(); - } - Navigator.of(context).push( - MaterialPageRoute( - builder: - (ctx) => NewMatrixUserPage( - pupil: pupil, - matrixId: - MatrixPolicyHelper.generateMatrixId( - isParent: true, - ), - displayName: - pupilSiblingsGroups != null - ? 'Fa. ${pupil.lastName} (E) $pupilSiblingsGroups' - : '${pupil.firstName} ${pupil.lastName.substring(0, 1).toUpperCase()}. (E) ${pupil.group}', - isParent: true, - ), + actionButton: pupil.tutorInfo?.parentsContact == null + ? IconButton( + onPressed: () { + String? pupilSiblingsGroups; + if (pupil.family != null) { + pupilSiblingsGroups = [ + ..._pupilManager.getSiblings(pupil), + pupil, + ].map((e) => e.group).toList().join(); + } + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => NewMatrixUserPage( + pupil: pupil, + matrixId: MatrixPolicyHelper.generateMatrixId( + isParent: true, + ), + displayName: pupilSiblingsGroups != null + ? 'Fa. ${pupil.lastName} (E) $pupilSiblingsGroups' + : '${pupil.firstName} ${pupil.lastName.substring(0, 1).toUpperCase()}. (E) ${pupil.group}', + isParent: true, ), + ), + ); + }, + icon: const Icon( + Icons.add_circle_outline, + size: 24, + color: AppColors.interactiveColor, + ), + ) + : IconButton( + onPressed: () async { + final confirmation = await confirmationDialog( + context: context, + title: 'Passwort zurücksetzen', + message: + 'Möchten Sie das Passwort wirklich zurücksetzen?', + ); + if (confirmation != true) return; + if (context.mounted) { + final logOutDevices = await logoutDevicesDialog( + context, ); - }, - icon: const Icon( - Icons.add_circle_outline, - size: 24, - color: AppColors.interactiveColor, - ), - ) - : IconButton( - onPressed: () async { - final confirmation = await confirmationDialog( - context: context, - title: 'Passwort zurücksetzen', - message: - 'Möchten Sie das Passwort wirklich zurücksetzen?', - ); - if (confirmation != true) return; - if (context.mounted) { - final logOutDevices = await logoutDevicesDialog( - context, - ); - if (logOutDevices == null) return; - final file = await _matrixPolicyManager.users - .resetPasswordAndPrintCredentialsFile( - user: - MatrixUserHelper.usersFromUserIds([ - pupil.tutorInfo!.parentsContact!, - ]).first, - logoutDevices: logOutDevices, - isStaff: false, - ); - if (file != null && context.mounted) { - Navigator.of(context).push( - MaterialPageRoute( - builder: - (context) => - PdfViewerPage(pdfFile: file), - ), + if (logOutDevices == null) return; + final file = await _matrixPolicyManager.users + .resetPasswordAndPrintCredentialsFile( + user: MatrixUserHelper.usersFromUserIds([ + pupil.tutorInfo!.parentsContact!, + ]).first, + logoutDevices: logOutDevices, + isStaff: false, ); - } + if (file != null && context.mounted) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + PdfViewerPage(pdfFile: file), + ), + ); } - }, - icon: const Icon( - Icons.qr_code_2_rounded, - size: 24, - color: AppColors.backgroundColor, - ), + } + }, + icon: const Icon( + Icons.qr_code_2_rounded, + size: 24, + color: AppColors.backgroundColor, ), + ), ), ], ), @@ -409,108 +390,106 @@ class PupilProfileInfosContent extends WatchingWidget { _buildInfoSection( icon: Icons.family_restroom_outlined, title: 'Geschwister', - child: - pupilSiblings.isNotEmpty - ? ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: pupilSiblings.length, - itemBuilder: (context, int index) { - PupilProxy sibling = pupilSiblings[index]; - return Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: Card( - color: AppColors.pupilProfileCardColor, - child: InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: - (ctx) => - PupilProfilePage(pupil: sibling), - ), - ); - }, - borderRadius: BorderRadius.circular(12), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - AvatarWithBadges(pupil: sibling, size: 60), - const Gap(16), - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - '${sibling.firstName} ${sibling.lastName}', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - color: AppColors.backgroundColor, - ), + child: pupilSiblings.isNotEmpty + ? ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: pupilSiblings.length, + itemBuilder: (context, int index) { + PupilProxy sibling = pupilSiblings[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Card( + color: AppColors.pupilProfileCardColor, + child: InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => + PupilProfilePage(pupil: sibling), + ), + ); + }, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AvatarWithBadges(pupil: sibling, size: 60), + const Gap(16), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '${sibling.firstName} ${sibling.lastName}', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: AppColors.backgroundColor, ), - const Gap(4), - Text( - 'Klasse ${sibling.group}', - style: TextStyle( - fontSize: 16, - color: Colors.grey.withValues( - alpha: 0.8, - ), + ), + const Gap(4), + Text( + 'Klasse ${sibling.group}', + style: TextStyle( + fontSize: 16, + color: Colors.grey.withValues( + alpha: 0.8, ), ), - const Gap(4), - Text( - 'Geburtsdatum: ${sibling.birthday.formatForUser()}', - style: TextStyle( - fontSize: 14, - color: Colors.grey.withValues( - alpha: 0.7, - ), + ), + const Gap(4), + Text( + 'Geburtsdatum: ${sibling.birthday.formatDateForUser()}', + style: TextStyle( + fontSize: 14, + color: Colors.grey.withValues( + alpha: 0.7, ), ), - ], - ), - ), - Icon( - Icons.arrow_forward_ios, - color: AppColors.interactiveColor, - size: 18, + ), + ], ), - ], - ), + ), + Icon( + Icons.arrow_forward_ios, + color: AppColors.interactiveColor, + size: 18, + ), + ], ), ), ), - ); - }, - ) - : Card( - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Icon( - Icons.info_outline, - color: Colors.grey.withValues(alpha: 0.6), - size: 24, - ), - const Gap(12), - Text( - 'Keine Geschwister erfasst', - style: TextStyle( - fontSize: 16, - color: Colors.grey.withValues(alpha: 0.7), - fontStyle: FontStyle.italic, - ), - ), - ], ), + ); + }, + ) + : Card( + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Icon( + Icons.info_outline, + color: Colors.grey.withValues(alpha: 0.6), + size: 24, + ), + const Gap(12), + Text( + 'Keine Geschwister erfasst', + style: TextStyle( + fontSize: 16, + color: Colors.grey.withValues(alpha: 0.7), + fontStyle: FontStyle.italic, + ), + ), + ], ), ), + ), ), const Gap(20), // Extra spacing at the bottom ], @@ -590,10 +569,9 @@ class PupilProfileInfosContent extends WatchingWidget { horizontal: 12, ), decoration: BoxDecoration( - color: - onTap != null - ? AppColors.interactiveColor.withValues(alpha: 0.1) - : Colors.transparent, + color: onTap != null + ? AppColors.interactiveColor.withValues(alpha: 0.1) + : Colors.transparent, borderRadius: BorderRadius.circular(8), ), child: Text( @@ -601,12 +579,11 @@ class PupilProfileInfosContent extends WatchingWidget { style: TextStyle( fontSize: 14, // Reduced from 16 to 14 fontWeight: FontWeight.w600, - color: - onTap != null - ? AppColors - .interactiveColor // Keep blue for interactive elements - : Colors - .black87, // Changed from AppColors.backgroundColor to black for non-interactive + color: onTap != null + ? AppColors + .interactiveColor // Keep blue for interactive elements + : Colors + .black87, // Changed from AppColors.backgroundColor to black for non-interactive ), ), ), @@ -668,12 +645,9 @@ class PupilProfileInfosContent extends WatchingWidget { horizontal: 12, ), decoration: BoxDecoration( - color: - onTap != null - ? AppColors.interactiveColor.withValues( - alpha: 0.1, - ) - : Colors.transparent, + color: onTap != null + ? AppColors.interactiveColor.withValues(alpha: 0.1) + : Colors.transparent, borderRadius: BorderRadius.circular(8), ), child: Text( @@ -681,12 +655,11 @@ class PupilProfileInfosContent extends WatchingWidget { style: TextStyle( fontSize: 14, // Reduced from 16 to 14 fontWeight: FontWeight.w600, - color: - onTap != null - ? AppColors - .interactiveColor // Keep blue for interactive elements - : Colors - .black87, // Changed from AppColors.backgroundColor to black for non-interactive + color: onTap != null + ? AppColors + .interactiveColor // Keep blue for interactive elements + : Colors + .black87, // Changed from AppColors.backgroundColor to black for non-interactive ), ), ), diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/learning_content/pupil_profile_learning_content.dart b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/learning_content/pupil_profile_learning_content.dart index 0ea8e8dc..8035f78e 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/learning_content/pupil_profile_learning_content.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/learning_content/pupil_profile_learning_content.dart @@ -1,7 +1,7 @@ import 'package:calendar_date_picker2/calendar_date_picker2.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/paddings.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; @@ -20,85 +20,134 @@ class PupilLearningContent extends WatchingWidget { watch(pupil); return Card( color: AppColors.pupilProfileCardColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), child: Padding( padding: AppPaddings.pupilProfileCardPadding, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Icon( - Icons.lightbulb, - color: AppColors.accentColor, - size: 24, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(Icons.lightbulb, color: AppColors.accentColor, size: 24), + Gap(5), + Text( + 'Lernen', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.backgroundColor, + ), + ), + ], ), - Gap(5), - Text('Lernen', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: AppColors.backgroundColor, - )) - ]), - const Gap(10), - Row( - children: [ - const Gap(5), - const Text('3 Jahre Eingangsphase?'), - const Gap(5), - InkWell( - onTap: () async { - final date = await showCalendarDatePicker2Dialog( - context: context, - config: CalendarDatePicker2WithActionButtonsConfig( - // selectableDayPredicate: (day) => - // !schooldayDates.any((element) => element.isSameDate(day)), - calendarType: CalendarDatePicker2Type.single, - ), - dialogSize: const Size(325, 400), - value: [], //schooldayDates, - borderRadius: BorderRadius.circular(15), - ); + const Gap(10), + Row( + children: [ + const Gap(5), + const Text('3 Jahre Eingangsphase?'), + const Gap(5), + InkWell( + onTap: () async { + final date = await showCalendarDatePicker2Dialog( + context: context, + config: CalendarDatePicker2WithActionButtonsConfig( + // selectableDayPredicate: (day) => + // !schooldayDates.any((element) => element.isSameDate(day)), + calendarType: CalendarDatePicker2Type.single, + ), + dialogSize: const Size(325, 400), + value: [], //schooldayDates, + borderRadius: BorderRadius.circular(15), + ); - if (date != null && date.isNotEmpty) { - di().updateSchoolyearHeldBackDate( + if (date != null && date.isNotEmpty) { + di().updateSchoolyearHeldBackDate( pupilId: pupil.pupilId, - date: (value: date.first!.toUtc())); - } - }, - onLongPress: () async { - if (pupil.schoolyearHeldBackAt == null) return; - final confirmation = await confirmationDialog( + date: (value: date.first!.toUtc()), + ); + } + }, + onLongPress: () async { + if (pupil.schoolyearHeldBackAt == null) return; + final confirmation = await confirmationDialog( context: context, title: 'Eintrag löschen', - message: 'Eintrag wirklich löschen?'); - if (confirmation != true) return; - di().updateSchoolyearHeldBackDate( - pupilId: pupil.internalId, date: (value: null)); - }, - child: Text( - pupil.schoolyearHeldBackAt != null - ? 'Entscheidung vom ${pupil.schoolyearHeldBackAt!.formatForUser()}' - : 'nein', - style: const TextStyle( - color: AppColors.interactiveColor, - fontWeight: FontWeight.bold, + message: 'Eintrag wirklich löschen?', + ); + if (confirmation != true) return; + di().updateSchoolyearHeldBackDate( + pupilId: pupil.internalId, + date: (value: null), + ); + }, + child: Text( + pupil.schoolyearHeldBackAt != null + ? 'Entscheidung vom ${pupil.schoolyearHeldBackAt!.formatDateForUser()}' + : 'nein', + style: const TextStyle( + color: AppColors.interactiveColor, + fontWeight: FontWeight.bold, + ), ), ), + ], + ), + + const Gap(5), + Row( + children: [ + const Gap(5), + const Text('Klassenleitung:'), + const Gap(5), + Text( + pupil.groupTutor ?? 'Kein Eintrag', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + + if (pupil.familyLanguageLessonsSince != null) ...[ + const Gap(5), + Row( + children: [ + const Gap(5), + const Text('HSU seit:'), + const Gap(5), + Text( + pupil.familyLanguageLessonsSince?.formatDateForUser() ?? + 'Kein Eintrag', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Text(pupil.language), + ], ), ], - ), - const Gap(10), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CompetenceChecksBadges(pupil: pupil), + if (pupil.religionLessonsSince != null) ...[ + const Gap(5), + Row( + children: [ + const Gap(5), + const Text('Religionsunterricht seit:'), + const Gap(5), + Text( + pupil.religionLessonsSince?.formatDateForUser() ?? + 'Kein Eintrag', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const Gap(5), + Text(pupil.religion ?? 'Kein Eintrag'), + ], + ), ], - ), - PupilLearningContentExpansionTileNavBar( - pupil: pupil, - ), - ]), + const Gap(10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [CompetenceChecksBadges(pupil: pupil)], + ), + PupilLearningContentExpansionTileNavBar(pupil: pupil), + ], + ), ), ); } diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/learning_support_content/support_level_history_expansion_tile.dart b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/learning_support_content/support_level_history_expansion_tile.dart index 1c40cf44..d1bc8dd6 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/learning_support_content/support_level_history_expansion_tile.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/learning_support_content/support_level_history_expansion_tile.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; import 'package:school_data_hub_flutter/app_utils/custom_encrypter.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; @@ -54,12 +54,11 @@ class _SupportLevelHistoryExpansionTileState const Text('Förderebene:', style: TextStyle(fontSize: 15.0)), const Gap(10), InkWell( - onTap: - () => supportLevelDialog( - context, - pupil, - pupil.latestSupportLevel!.level, - ), + onTap: () => supportLevelDialog( + context, + pupil, + pupil.latestSupportLevel!.level, + ), child: Text( pupil.latestSupportLevel == null ? 'kein Eintrag' @@ -83,102 +82,105 @@ class _SupportLevelHistoryExpansionTileState children: [ pupil.supportLevelHistory != null ? ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: widget.pupil.supportLevelHistory!.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: GestureDetector( - onLongPress: () async { - final confirmation = await confirmationDialog( - context: context, - title: 'Eintrag löschen', - message: 'Eintrag wirklich löschen?', - ); - if (confirmation != true) return; - if (_hubSessionManager.isAdmin) { - PupilMutator().deleteSupportLevelHistoryItem( - pupilId: pupil.pupilId, - supportLevelId: plans[index].id!, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.pupil.supportLevelHistory!.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: GestureDetector( + onLongPress: () async { + final confirmation = await confirmationDialog( + context: context, + title: 'Eintrag löschen', + message: 'Eintrag wirklich löschen?', ); - } - }, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.pupil.supportLevelHistory![index].createdAt - .formatForUser(), - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 18, + if (confirmation != true) return; + if (_hubSessionManager.isAdmin) { + PupilMutator().deleteSupportLevelHistoryItem( + pupilId: pupil.pupilId, + supportLevelId: plans[index].id!, + ); + } + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget + .pupil + .supportLevelHistory![index] + .createdAt + .formatDateForUser(), + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), - ), - const Gap(20), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Text( - 'Förderebene ', - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 18, + const Gap(20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text( + 'Förderebene ', + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), - ), - Text( - widget - .pupil - .supportLevelHistory![index] - .level - .toString(), - style: const TextStyle( - color: AppColors.backgroundColor, - fontWeight: FontWeight.bold, - fontSize: 18, + Text( + widget + .pupil + .supportLevelHistory![index] + .level + .toString(), + style: const TextStyle( + color: AppColors.backgroundColor, + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), - ), - ], - ), - Row( - children: [ - Text( - pupil - .supportLevelHistory![index] - .comment != - '' - ? customEncrypter.decryptString( - pupil - .supportLevelHistory![index] - .comment, - ) - : '', - style: const TextStyle(fontSize: 14), - ), - ], + ], + ), + Row( + children: [ + Text( + pupil + .supportLevelHistory![index] + .comment != + '' + ? customEncrypter.decryptString( + pupil + .supportLevelHistory![index] + .comment, + ) + : '', + style: const TextStyle(fontSize: 14), + ), + ], + ), + ], + ), + const Spacer(), + Text( + pupil.supportLevelHistory![index].createdBy, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 18, ), - ], - ), - const Spacer(), - Text( - pupil.supportLevelHistory![index].createdBy, - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 18, ), - ), - const Gap(10), - ], + const Gap(10), + ], + ), ), - ), - ); - }, - ) + ); + }, + ) : const Text('keine Einträge'), ], ), diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/pupil_profile_page_content.dart b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/pupil_profile_page_content.dart index 4097490e..f4207c85 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/pupil_profile_page_content.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/pupil_profile_page_content.dart @@ -4,6 +4,7 @@ import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/features/app_main_navigation/domain/main_menu_bottom_nav_manager.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_navigation.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/after_school_care_content/pupil_ogs_content.dart'; import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/authorization_content/pupil_profile_authorization_content.dart'; import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/communication_content/pupil_profile_communication_content.dart'; import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/credit/pupil_profile_credit_content.dart'; @@ -44,15 +45,16 @@ class PupilProfilePageContent extends WatchingWidget { duration: const Duration(milliseconds: 300), transitionBuilder: (Widget child, Animation animation) { return SlideTransition( - position: Tween( - begin: const Offset(0.1, 0), - end: Offset.zero, - ).animate( - CurvedAnimation( - parent: animation, - curve: Curves.easeInOut, - ), - ), + position: + Tween( + begin: const Offset(0.1, 0), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: animation, + curve: Curves.easeInOut, + ), + ), child: FadeTransition(opacity: animation, child: child), ); }, @@ -100,6 +102,8 @@ class PupilProfilePageContent extends WatchingWidget { return PupilAttendanceContent(pupil: pupil); } else if (navState == ProfileNavigationState.schooldayEvent.value) { return PupilProfileSchooldayEventsContent(pupil: pupil); + } else if (navState == ProfileNavigationState.ogs.value) { + return PupilOgsContent(pupil: pupil); } else if (navState == ProfileNavigationState.lists.value) { return PupilSchoolListsContentCard(pupil: pupil); } else if (navState == ProfileNavigationState.authorization.value) { diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/religion_list_page.dart b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/religion_list_page.dart new file mode 100644 index 00000000..72f84a50 --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/religion_list_page.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/theme/styles.dart'; +import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_sliver_list.dart'; +import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_sliver_search_app_bar.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/pupil_manager.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/religion_page/widgets/religion_card.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/religion_page/widgets/religion_list_page_bottom_navbar.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/religion_page/widgets/religion_list_search_bar.dart'; +import 'package:watch_it/watch_it.dart'; + +final _filterStateManager = di(); + +final _pupilManager = di(); + +List religionFilter(List pupils) { + List filteredPupils = []; + bool filtersOn = false; + for (PupilProxy pupil in pupils) { + if (pupil.religionLessonsSince == null) { + filtersOn = true; + continue; + } + + filteredPupils.add(pupil); + } + if (filtersOn) { + _filterStateManager.setFilterState( + filterState: FilterState.pupil, value: true); + } + return filteredPupils; +} + +void _onPop(bool didPop, dynamic result) { + _filterStateManager.resetFilters(); +} + +class ReligionListPage extends WatchingWidget { + const ReligionListPage({super.key}); + + @override + Widget build(BuildContext context) { + List filteredPupils = + watchValue((PupilsFilter x) => x.filteredPupils); + List pupils = religionFilter(filteredPupils); + onDispose(() { + _filterStateManager.resetFilters(); + }); + return PopScope( + onPopInvokedWithResult: (didPop, result) => _onPop(didPop, result), + child: Scaffold( + backgroundColor: AppColors.canvasColor, + appBar: AppBar( + centerTitle: true, + backgroundColor: AppColors.backgroundColor, + title: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.church, + size: 25, + color: Colors.white, + ), + Gap(10), + Text( + 'Religion', + style: AppStyles.appBarTextStyle, + ), + ], + ), + automaticallyImplyLeading: false, + ), + body: RefreshIndicator( + onRefresh: () async => _pupilManager.updatePupilList(pupils), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 700), + child: CustomScrollView( + slivers: [ + const SliverGap(5), + GenericSliverSearchAppBar( + height: 110, + title: ReligionListSearchBar( + pupils: pupils, + ), + ), + GenericSliverListWithEmptyListCheck( + items: pupils, + itemBuilder: (_, pupil) => ReligionCard(pupil)), + ], + ), + ), + ), + ), + bottomNavigationBar: ReligionListPageBottomNavBar(), + ), + ); + } +} + diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_card.dart b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_card.dart new file mode 100644 index 00000000..a866764e --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_card.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; +import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; +import 'package:school_data_hub_flutter/features/app_main_navigation/domain/main_menu_bottom_nav_manager.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/pupil_profile_page/pupil_profile_page.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/avatar.dart'; +import 'package:watch_it/watch_it.dart'; + +final _mainMenuBottomNavManager = di(); +final _filterStateManager = di(); + +class ReligionCard extends WatchingWidget { + final PupilProxy pupil; + const ReligionCard(this.pupil, {super.key}); + @override + Widget build(BuildContext context) { + return Card( + color: Colors.white, + surfaceTintColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + elevation: 1.0, + margin: const EdgeInsets.only( + left: 4.0, + right: 4.0, + top: 4.0, + bottom: 4.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AvatarWithBadges(pupil: pupil, size: 80), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(15), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: InkWell( + onTap: () { + _filterStateManager.resetFilters(); + _mainMenuBottomNavManager + .setPupilProfileNavPage(0); + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => + PupilProfilePage(pupil: pupil), + ), + ); + }, + child: Row( + children: [ + Text( + pupil.firstName, + overflow: TextOverflow.fade, + softWrap: false, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + const Gap(5), + Text( + pupil.lastName, + overflow: TextOverflow.fade, + softWrap: false, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.normal, + fontSize: 18, + ), + ), + const Gap(5), + ], + ), + ), + ), + ), + ], + ), + + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Religion:'), + const Gap(10), + Flexible( + child: InkWell( + onTap: () {}, + child: Text( + pupil.religion ?? 'keine Angabe', + overflow: TextOverflow.ellipsis, + softWrap: true, + maxLines: 3, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + const Gap(5), + Row( + children: [ + Text('Angemeldet seit:'), + const Gap(10), + Text( + pupil.religionLessonsSince != null + ? pupil.religionLessonsSince!.formatDateForUser() + : 'keine Angabe', + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_filter_bottom_sheet.dart b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_filter_bottom_sheet.dart new file mode 100644 index 00000000..d9de9e4c --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_filter_bottom_sheet.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/theme/styles.dart'; +import 'package:school_data_hub_flutter/common/widgets/themed_filter_chip.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/common_pupil_filters.dart'; +import 'package:watch_it/watch_it.dart'; + +class ReligionFilterBottomSheet extends WatchingWidget { + const ReligionFilterBottomSheet({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + children: [ + CommonPupilFiltersWidget(), + Gap(10), + ReligionFiltersSection(), + ], + ); + } +} + +class ReligionFiltersSection extends WatchingWidget { + const ReligionFiltersSection({super.key}); + + @override + Widget build(BuildContext context) { + final religionCourseFilters = di().religionCourseFilters; + + return Column( + children: [ + const Row( + children: [ + Text( + 'Religion', + style: AppStyles.subtitle, + ) + ], + ), + const Gap(5), + Wrap( + spacing: 5, + crossAxisAlignment: WrapCrossAlignment.center, + alignment: WrapAlignment.center, + children: [ + for (final religionFilter in religionCourseFilters) + ThemedFilterChip( + label: religionFilter.displayName, + selected: watch(religionFilter).isActive, + onSelected: (val) { + religionFilter.toggle(val); + }, + ), + ], + ), + ], + ); + } +} + diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_list_page_bottom_navbar.dart b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_list_page_bottom_navbar.dart new file mode 100644 index 00000000..2ee893f3 --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_list_page_bottom_navbar.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/widgets/bottom_nav_bar_layouts.dart'; +import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_filter_bottom_sheet.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/religion_page/widgets/religion_filter_bottom_sheet.dart'; +import 'package:watch_it/watch_it.dart'; + +final _pupilsFilter = di(); + +class ReligionListPageBottomNavBar extends WatchingWidget { + const ReligionListPageBottomNavBar({super.key}); + + @override + Widget build(BuildContext context) { + final filtersOn = watchValue((FiltersStateManager x) => x.filtersActive); + return BottomNavBarLayout( + bottomNavBar: BottomAppBar( + height: 60, + padding: const EdgeInsets.all(10), + shape: null, + color: AppColors.backgroundColor, + child: IconTheme( + data: IconThemeData(color: Theme.of(context).colorScheme.onPrimary), + child: Row( + children: [ + const Spacer(), + IconButton( + tooltip: 'zurück', + icon: const Icon( + Icons.arrow_back, + size: 30, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + const Gap(30), + InkWell( + onTap: () => + showGenericFilterBottomSheet(context: context, filterList: [ + const ReligionFilterBottomSheet(), + ]), + onLongPress: () => _pupilsFilter.resetFilters(), + child: Icon( + Icons.filter_list, + color: filtersOn ? Colors.deepOrange : Colors.white, + size: 30, + ), + ), + const Gap(15) + ], + ), + ), + ), + ); + } +} + diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_list_search_bar.dart b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_list_search_bar.dart new file mode 100644 index 00000000..43b7e1e3 --- /dev/null +++ b/school_data_hub_flutter/lib/features/pupil/presentation/religion_page/widgets/religion_list_search_bar.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/domain/models/enums.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/widgets/filter_button.dart'; +import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_filter_bottom_sheet.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/religion_page/widgets/religion_filter_bottom_sheet.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/pupil_search_text_field.dart'; +import 'package:watch_it/watch_it.dart'; + +final _pupilsFilter = di(); + +class ReligionListSearchBar extends WatchingWidget { + final List pupils; + const ReligionListSearchBar({required this.pupils, super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.canvasColor, + borderRadius: BorderRadius.circular(5.0), + ), + child: Column( + children: [ + const Gap(5), + Padding( + padding: const EdgeInsets.only(left: 10.0, right: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.people_alt_rounded, + color: AppColors.backgroundColor, + ), + const Gap(5), + Text( + pupils.length.toString(), + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Row( + children: [ + Expanded( + child: PupilSearchTextField( + searchType: SearchType.pupil, + hintText: 'Schüler/in suchen', + refreshFunction: _pupilsFilter.refreshs)), + FilterButton( + isSearchBar: true, + showBottomSheetFunction: () => showGenericFilterBottomSheet( + context: context, filterList: [ + const ReligionFilterBottomSheet(), + ]), + ) + ], + ), + ), + ], + ), + ); + } +} + diff --git a/school_data_hub_flutter/lib/features/pupil/presentation/select_pupils_list_page/widgets/select_pupils_filter_bottom_sheet.dart b/school_data_hub_flutter/lib/features/pupil/presentation/select_pupils_list_page/widgets/select_pupils_filter_bottom_sheet.dart index 6099a109..e6832827 100644 --- a/school_data_hub_flutter/lib/features/pupil/presentation/select_pupils_list_page/widgets/select_pupils_filter_bottom_sheet.dart +++ b/school_data_hub_flutter/lib/features/pupil/presentation/select_pupils_list_page/widgets/select_pupils_filter_bottom_sheet.dart @@ -21,9 +21,9 @@ class SelectPupilsFilterBottomSheet extends WatchingWidget { (PupilFilterManager x) => x.pupilFilterState, ); - bool valueOgs = activePupilFilters[PupilFilter.ogs]!; + bool valueOgs = activePupilFilters[PupilFilter.afterSchoolCare]!; - bool valueNotOgs = activePupilFilters[PupilFilter.notOgs]!; + bool valueNotOgs = activePupilFilters[PupilFilter.noAfterSchoolCare]!; //- LEARNING SUPPORT FILTERS Map supportLevelFilters = watchValue( @@ -50,6 +50,7 @@ class SelectPupilsFilterBottomSheet extends WatchingWidget { bool valueSupportAreaLearning = supportAreaFilters[SupportArea.learning]!; bool valueSupportAreaGerman = supportAreaFilters[SupportArea.german]!; bool valueSupportAreaLanguage = supportAreaFilters[SupportArea.language]!; + final religionCourseFilters = di().religionCourseFilters; // bool valueSupportAreaTurkish = supportAreaFilters[PupilFilter.turkishClass]; // bool valueSupportAreaArabic = supportAreaFilters[PupilFilter.arabicClass]!; // bool valueSupportAreaAlbanian = @@ -93,8 +94,8 @@ class SelectPupilsFilterBottomSheet extends WatchingWidget { _pupilFilterLocator.setPupilFilter( pupilFilterRecords: [ - (filter: PupilFilter.notOgs, value: false), - (filter: PupilFilter.ogs, value: val), + (filter: PupilFilter.noAfterSchoolCare, value: false), + (filter: PupilFilter.afterSchoolCare, value: val), ], ); return; @@ -102,7 +103,7 @@ class SelectPupilsFilterBottomSheet extends WatchingWidget { _pupilFilterLocator.setPupilFilter( pupilFilterRecords: [ - (filter: PupilFilter.ogs, value: val), + (filter: PupilFilter.afterSchoolCare, value: val), ], ); }, @@ -115,15 +116,15 @@ class SelectPupilsFilterBottomSheet extends WatchingWidget { // in case not ogs is selected, ogs should be deselected _pupilFilterLocator.setPupilFilter( pupilFilterRecords: [ - (filter: PupilFilter.ogs, value: false), - (filter: PupilFilter.notOgs, value: val), + (filter: PupilFilter.afterSchoolCare, value: false), + (filter: PupilFilter.noAfterSchoolCare, value: val), ], ); return; } _pupilFilterLocator.setPupilFilter( pupilFilterRecords: [ - (filter: PupilFilter.notOgs, value: val), + (filter: PupilFilter.noAfterSchoolCare, value: val), ], ); }, @@ -297,6 +298,23 @@ class SelectPupilsFilterBottomSheet extends WatchingWidget { ), ], ), + const Row(children: [Text('Jahrgang', style: AppStyles.subtitle)]), + const Gap(5), + Wrap( + spacing: 5, + crossAxisAlignment: WrapCrossAlignment.center, + alignment: WrapAlignment.center, + children: [ + for (final religionCourseFilter in religionCourseFilters) + ThemedFilterChip( + label: religionCourseFilter.displayName, + selected: watch(religionCourseFilter).isActive, + onSelected: (val) { + religionCourseFilter.toggle(val); + }, + ), + ], + ), const Gap(20), ], ), diff --git a/school_data_hub_flutter/lib/features/school_calendar/presentation/new_school_semester_page/schooldays_calendar_page/schooldays_calendar_page.dart b/school_data_hub_flutter/lib/features/school_calendar/presentation/new_school_semester_page/schooldays_calendar_page/schooldays_calendar_page.dart index b8d44a30..c4d4deb2 100644 --- a/school_data_hub_flutter/lib/features/school_calendar/presentation/new_school_semester_page/schooldays_calendar_page/schooldays_calendar_page.dart +++ b/school_data_hub_flutter/lib/features/school_calendar/presentation/new_school_semester_page/schooldays_calendar_page/schooldays_calendar_page.dart @@ -2,7 +2,7 @@ import 'package:calendar_date_picker2/calendar_date_picker2.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:intl/intl.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; @@ -43,8 +43,9 @@ class SchooldaysCalendarState extends State { @override Widget build(BuildContext context) { final schooldays = watchValue((SchoolCalendarManager x) => x.schooldays); - final schooldayDates = - schooldays.map((e) => e.schoolday.toLocal()).toList(); + final schooldayDates = schooldays + .map((e) => e.schoolday.toLocal()) + .toList(); // List missedSchooldays = // di().getMissedSchooldayesOnADay(_selectedDay!); return Scaffold( @@ -79,11 +80,8 @@ class SchooldaysCalendarState extends State { var results = await showCalendarDatePicker2Dialog( context: context, config: CalendarDatePicker2WithActionButtonsConfig( - selectableDayPredicate: - (day) => - !schooldayDates.any( - (element) => element.isSameDate(day), - ), + selectableDayPredicate: (day) => + !schooldayDates.any((element) => element.isSameDate(day)), calendarType: CalendarDatePicker2Type.multi, ), dialogSize: const Size(325, 400), @@ -145,10 +143,9 @@ class SchooldaysCalendarState extends State { availableCalendarFormats: const { CalendarFormat.month: 'Month', }, - enabledDayPredicate: - (day) => schooldayDates.any( - (element) => element.isSameDate(day), - ), + enabledDayPredicate: (day) => schooldayDates.any( + (element) => element.isSameDate(day), + ), calendarBuilders: CalendarBuilders( singleMarkerBuilder: null, // singleMarkerBuilder: (context, date, events) => @@ -173,19 +170,18 @@ class SchooldaysCalendarState extends State { // day.day.toString(), // style: TextStyle(color: Colors.white), // )), - todayBuilder: - (context, date, events) => Container( - margin: const EdgeInsets.all(4.0), - alignment: Alignment.center, - decoration: BoxDecoration( - color: Theme.of(context).highlightColor, - borderRadius: BorderRadius.circular(10.0), - ), - child: Text( - date.day.toString(), - style: const TextStyle(color: Colors.white), - ), - ), + todayBuilder: (context, date, events) => Container( + margin: const EdgeInsets.all(4.0), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Theme.of(context).highlightColor, + borderRadius: BorderRadius.circular(10.0), + ), + child: Text( + date.day.toString(), + style: const TextStyle(color: Colors.white), + ), + ), ), firstDay: kFirstDay, lastDay: kLastDay, @@ -206,7 +202,7 @@ class SchooldaysCalendarState extends State { context: context, title: 'Schultag löschen', message: - 'Möchtest du den Schultag ${selectedDay.formatForUser()} wirklich löschen?', + 'Möchtest du den Schultag ${selectedDay.formatDateForUser()} wirklich löschen?', ); if (confirm == null || !confirm) return; await _schoolCalendarManager.deleteSchoolday(selectedDay); @@ -227,53 +223,52 @@ class SchooldaysCalendarState extends State { ), ), SliverToBoxAdapter( - child: - _selectedDay != null - ? Row( - children: [ - const Gap(15), - Text( - DateFormat( - 'EEEE', - Localizations.localeOf(context).toString(), - ).format(_selectedDay!), - style: const TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.bold, - ), + child: _selectedDay != null + ? Row( + children: [ + const Gap(15), + Text( + DateFormat( + 'EEEE', + Localizations.localeOf(context).toString(), + ).format(_selectedDay!), + style: const TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, ), - const Gap(5), - Text( - ' ${_selectedDay?.formatForUser()}', - style: const TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.bold, - ), + ), + const Gap(5), + Text( + ' ${_selectedDay?.formatDateForUser()}', + style: const TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, ), - const Gap(40), - // Text( - // missedSchooldays - // .where((missedSchoolday) => - // missedSchoolday.missedType == 'missed') - // .length - // .toString(), - // style: const TextStyle( - // fontSize: 28.0, fontWeight: FontWeight.bold)), - // const Gap(20) - ], - ) - : const Row( - children: [ - Gap(15), - Text( - 'Kein Tag ausgewählt', - style: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.bold, - ), + ), + const Gap(40), + // Text( + // missedSchooldays + // .where((missedSchoolday) => + // missedSchoolday.missedType == 'missed') + // .length + // .toString(), + // style: const TextStyle( + // fontSize: 28.0, fontWeight: FontWeight.bold)), + // const Gap(20) + ], + ) + : const Row( + children: [ + Gap(15), + Text( + 'Kein Tag ausgewählt', + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, ), - ], - ), + ), + ], + ), ), // SliverList( // delegate: SliverChildBuilderDelegate( diff --git a/school_data_hub_flutter/lib/features/school_calendar/presentation/new_school_semester_page/widgets/date_picker_button.dart b/school_data_hub_flutter/lib/features/school_calendar/presentation/new_school_semester_page/widgets/date_picker_button.dart index 71cadbec..ce01d5d8 100644 --- a/school_data_hub_flutter/lib/features/school_calendar/presentation/new_school_semester_page/widgets/date_picker_button.dart +++ b/school_data_hub_flutter/lib/features/school_calendar/presentation/new_school_semester_page/widgets/date_picker_button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; class DatePickerButton extends StatelessWidget { final DateTime? dateToSelect; @@ -26,7 +26,7 @@ class DatePickerButton extends StatelessWidget { }, child: Text( dateToSelect != null - ? dateToSelect!.formatForUser() + ? dateToSelect!.formatDateForUser() : 'Bitte auswählen', style: const TextStyle( color: Colors.black, diff --git a/school_data_hub_flutter/lib/features/school_lists/services/school_list_pdf_generator.dart b/school_data_hub_flutter/lib/features/school_lists/services/school_list_pdf_generator.dart index 649a130a..54ff6ded 100644 --- a/school_data_hub_flutter/lib/features/school_lists/services/school_list_pdf_generator.dart +++ b/school_data_hub_flutter/lib/features/school_lists/services/school_list_pdf_generator.dart @@ -8,7 +8,7 @@ import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_app_bar.dart'; @@ -95,8 +95,9 @@ class SchoolListPdfGenerator { int currentIndex = 0; for (int page = 1; page <= totalPages; page++) { - final int pupilsOnThisPage = - page == 1 ? maxPupilsFirstPage : maxPupilsPerPage; + final int pupilsOnThisPage = page == 1 + ? maxPupilsFirstPage + : maxPupilsPerPage; final endIndex = (currentIndex + pupilsOnThisPage).clamp( 0, pupilEntries.length, @@ -131,7 +132,7 @@ class SchoolListPdfGenerator { // Get the proper directory for saving files final directory = await getApplicationDocumentsDirectory(); final fileName = - "Schulliste_${schoolList.name}_${DateTime.now().formatForUser()}.pdf"; + "Schulliste_${schoolList.name}_${DateTime.now().formatDateForUser()}.pdf"; final file = File('${directory.path}/$fileName'); await file.writeAsBytes(await pdf.save()); @@ -199,9 +200,8 @@ class SchoolListPdfGenerator { } catch (e) { // Return a minimal page return pw.Page( - build: - (context) => - pw.Center(child: pw.Text('Error on page $pageNumber: $e')), + build: (context) => + pw.Center(child: pw.Text('Error on page $pageNumber: $e')), ); } } @@ -546,7 +546,7 @@ class SchoolListPdfGenerator { style: pw.TextStyle(fontSize: 8, font: fontRegular), ), pw.Text( - 'Erstellt am: ${DateTime.now().formatForUser()}', + 'Erstellt am: ${DateTime.now().formatDateForUser()}', style: pw.TextStyle(fontSize: 8, font: fontRegular), ), ], diff --git a/school_data_hub_flutter/lib/features/statistics/chart_page/chart_page.dart b/school_data_hub_flutter/lib/features/statistics/chart_page/chart_page.dart new file mode 100644 index 00000000..43c1d0a6 --- /dev/null +++ b/school_data_hub_flutter/lib/features/statistics/chart_page/chart_page.dart @@ -0,0 +1,395 @@ +import 'package:community_charts_flutter/community_charts_flutter.dart' as charts; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:intl/intl.dart'; +import 'package:school_data_hub_client/school_data_hub_client.dart'; +import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; +import 'package:school_data_hub_flutter/common/theme/styles.dart'; + +class ChartPage extends StatelessWidget { + final Map chartData; + final List schooldays; + + const ChartPage({ + super.key, + required this.chartData, + required this.schooldays, + }); + + // Sort schooldays by date - make it accessible + List get sortedSchooldays { + return List.from(schooldays) + ..sort((a, b) => a.schoolday.compareTo(b.schoolday)); + } + + /// Gets the first schoolday of each month for tick labels + Set _getFirstOfMonthDates() { + final Set firstOfMonthDates = {}; + String? currentMonth; + + for (final schoolday in sortedSchooldays) { + final date = schoolday.schoolday.toLocal(); + final monthKey = '${date.year}-${date.month}'; + + // If this is a new month, add the first schoolday of that month + if (monthKey != currentMonth) { + currentMonth = monthKey; + firstOfMonthDates.add(_formatDateForChart(schoolday.schoolday)); + } + } + + return firstOfMonthDates; + } + + List> _createSeries() { + // Sort schooldays by date + final sorted = sortedSchooldays; + + final specialNeedsData = sorted.map((schoolday) { + final data = chartData[schoolday.schoolday]; + final dateStr = _formatDateForChart(schoolday.schoolday); + return ChartData( + date: schoolday.schoolday, + dateString: dateStr, + count: data?.specialNeeds ?? 0, + seriesId: 'specialNeeds', + ); + }).toList(); + + final migrationSupportData = sorted.map((schoolday) { + final data = chartData[schoolday.schoolday]; + final dateStr = _formatDateForChart(schoolday.schoolday); + return ChartData( + date: schoolday.schoolday, + dateString: dateStr, + count: data?.migrationSupport ?? 0, + seriesId: 'migrationSupport', + ); + }).toList(); + + final supportLevel3Data = sorted.map((schoolday) { + final data = chartData[schoolday.schoolday]; + final dateStr = _formatDateForChart(schoolday.schoolday); + return ChartData( + date: schoolday.schoolday, + dateString: dateStr, + count: data?.supportLevel3 ?? 0, + seriesId: 'supportLevel3', + ); + }).toList(); + + final regularPupilsData = sorted.map((schoolday) { + final data = chartData[schoolday.schoolday]; + final dateStr = _formatDateForChart(schoolday.schoolday); + return ChartData( + date: schoolday.schoolday, + dateString: dateStr, + count: data?.regularPupils ?? 0, + seriesId: 'regularPupils', + ); + }).toList(); + + final newPupilsData = sorted.map((schoolday) { + final data = chartData[schoolday.schoolday]; + final dateStr = _formatDateForChart(schoolday.schoolday); + return ChartData( + date: schoolday.schoolday, + dateString: dateStr, + count: data?.newPupils ?? 0, + seriesId: 'newPupils', + ); + }).toList(); + + return [ + charts.Series( + id: 'Special Needs', + colorFn: (_, __) => charts.ColorUtil.fromDartColor( + AppColors.accentColor, + ), + domainFn: (ChartData data, _) => data.dateString, + measureFn: (ChartData data, _) => data.count, + data: specialNeedsData, + ), + charts.Series( + id: 'Migration Support', + colorFn: (_, __) => charts.ColorUtil.fromDartColor( + AppColors.backgroundColor, + ), + domainFn: (ChartData data, _) => data.dateString, + measureFn: (ChartData data, _) => data.count, + data: migrationSupportData, + ), + charts.Series( + id: 'Support Level 3', + colorFn: (_, __) => charts.ColorUtil.fromDartColor( + AppColors.warningButtonColor, + ), + domainFn: (ChartData data, _) => data.dateString, + measureFn: (ChartData data, _) => data.count, + data: supportLevel3Data, + ), + charts.Series( + id: 'Regular Pupils', + colorFn: (_, __) => charts.ColorUtil.fromDartColor( + const Color.fromARGB(255, 200, 200, 200), // Light gray for regular pupils + ), + domainFn: (ChartData data, _) => data.dateString, + measureFn: (ChartData data, _) => data.count, + data: regularPupilsData, + ), + charts.Series( + id: 'New Pupils', + colorFn: (_, __) => charts.ColorUtil.fromDartColor( + AppColors.successButtonColor, + ), + domainFn: (ChartData data, _) => data.dateString, + measureFn: (ChartData data, _) => data.count, + data: newPupilsData, + )..setAttribute(charts.rendererIdKey, 'lineSeries'), + ]; + } + + String _formatDateForChart(DateTime date) { + final localDate = date.isUtc ? date.toLocal() : date; + return DateFormat('dd.MM').format(localDate); + } + + + @override + Widget build(BuildContext context) { + if (schooldays.isEmpty) { + return Scaffold( + backgroundColor: AppColors.canvasColor, + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: AppColors.backgroundColor, + centerTitle: true, + title: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.bar_chart_rounded, size: 25, color: Colors.white), + Gap(10), + Text('Statistik Diagramm', style: AppStyles.appBarTextStyle), + ], + ), + ), + body: const Center( + child: Text('Keine Daten verfügbar'), + ), + ); + } + + final series = _createSeries(); + + return Scaffold( + backgroundColor: AppColors.canvasColor, + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: AppColors.backgroundColor, + centerTitle: true, + title: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.bar_chart_rounded, size: 25, color: Colors.white), + Gap(10), + Text('Statistik Diagramm', style: AppStyles.appBarTextStyle), + ], + ), + ), + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(15), + const Text( + 'Schülerzahlen nach Schultag', + style: AppStyles.title, + ), + const Gap(10), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxHeight <= 0 || constraints.maxWidth <= 0) { + return const Center( + child: Text('Chart wird geladen...'), + ); + } + return charts.OrdinalComboChart( + series, + animate: true, + animationDuration: const Duration(milliseconds: 500), + defaultRenderer: charts.LineRendererConfig( + includeArea: true, + stacked: true, + ), + customSeriesRenderers: [ + charts.LineRendererConfig( + customRendererId: 'lineSeries', + strokeWidthPx: 3.0, // Make the line thicker + includeArea: false, + stacked: false, + ), + ], + layoutConfig: charts.LayoutConfig( + leftMarginSpec: charts.MarginSpec.fixedPixel(60), + topMarginSpec: charts.MarginSpec.fixedPixel(20), + rightMarginSpec: charts.MarginSpec.fixedPixel(40), + bottomMarginSpec: charts.MarginSpec.fixedPixel(60), + ), + primaryMeasureAxis: const charts.NumericAxisSpec( + tickProviderSpec: charts.BasicNumericTickProviderSpec( + zeroBound: true, + ), + ), + domainAxis: charts.OrdinalAxisSpec( + tickProviderSpec: charts.StaticOrdinalTickProviderSpec( + sortedSchooldays.map((schoolday) { + final dateStr = _formatDateForChart(schoolday.schoolday); + final firstOfMonthDates = _getFirstOfMonthDates(); + + // Only show label if it's the first of a month + if (firstOfMonthDates.contains(dateStr)) { + try { + final date = DateFormat('dd.MM').parse(dateStr); + return charts.TickSpec( + dateStr, + label: DateFormat('MMM').format(date), + ); + } catch (e) { + return charts.TickSpec(dateStr); + } + } else { + // Return tick with empty label for other dates + return charts.TickSpec( + dateStr, + label: '', + ); + } + }).toList(), + ), + ), + behaviors: [ + charts.SeriesLegend( + position: charts.BehaviorPosition.bottom, + desiredMaxRows: 4, + cellPadding: const EdgeInsets.only( + right: 4.0, + bottom: 4.0, + ), + ), + charts.ChartTitle( + 'Schultag', + behaviorPosition: charts.BehaviorPosition.bottom, + titleOutsideJustification: + charts.OutsideJustification.middleDrawArea, + ), + charts.ChartTitle( + 'Anzahl Schüler', + behaviorPosition: charts.BehaviorPosition.start, + titleOutsideJustification: + charts.OutsideJustification.middleDrawArea, + ), + ], + ); + }, + ), + ), + const Gap(20), + Wrap( + alignment: WrapAlignment.center, + spacing: 20, + runSpacing: 10, + children: [ + _buildLegendItem( + 'Besonderer Förderbedarf', + AppColors.accentColor, + ), + _buildLegendItem( + 'Migrationsunterstützung', + AppColors.backgroundColor, + ), + _buildLegendItem( + 'Förderstufe 3', + AppColors.warningButtonColor, + ), + _buildLegendItem( + 'Reguläre Schüler', + const Color.fromARGB(255, 200, 200, 200), + ), + _buildLegendItem( + 'Neue Schüler', + AppColors.successButtonColor, + ), + ], + ), + const Gap(20), + ], + ), + ), + ), + ), + bottomNavigationBar: BottomAppBar( + padding: const EdgeInsets.all(10), + shape: null, + color: AppColors.backgroundColor, + child: IconTheme( + data: IconThemeData(color: Theme.of(context).colorScheme.onPrimary), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: Row( + children: [ + IconButton( + tooltip: 'zurück', + icon: const Icon(Icons.arrow_back, size: 30), + onPressed: () { + Navigator.pop(context); + }, + ), + const Spacer(), + ], + ), + ), + ), + ), + ); + } + + Widget _buildLegendItem(String label, Color color) { + return Row( + children: [ + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(4), + ), + ), + const Gap(8), + Text( + label, + style: const TextStyle(fontSize: 14), + ), + ], + ); + } +} + +class ChartData { + final DateTime date; + final String dateString; + final int count; + final String seriesId; + + ChartData({ + required this.date, + required this.dateString, + required this.count, + required this.seriesId, + }); +} + diff --git a/school_data_hub_flutter/lib/features/statistics/chart_page/chart_page_controller.dart b/school_data_hub_flutter/lib/features/statistics/chart_page/chart_page_controller.dart new file mode 100644 index 00000000..a613e009 --- /dev/null +++ b/school_data_hub_flutter/lib/features/statistics/chart_page/chart_page_controller.dart @@ -0,0 +1,365 @@ +import 'package:flutter/material.dart'; +import 'package:school_data_hub_client/school_data_hub_client.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/pupil_manager.dart'; +import 'package:school_data_hub_flutter/features/school_calendar/domain/school_calendar_manager.dart'; +import 'package:school_data_hub_flutter/features/statistics/chart_page/chart_page.dart'; +import 'package:watch_it/watch_it.dart'; + +class ChartPageController extends StatefulWidget { + const ChartPageController({super.key}); + + @override + State createState() => _ChartPageControllerState(); +} + +class _ChartPageControllerState extends State { + late final PupilManager _pupilManager; + late final SchoolCalendarManager _schoolCalendarManager; + + @override + void initState() { + super.initState(); + _pupilManager = di(); + _schoolCalendarManager = di(); + } + + /// Gets schooldays for the current semester + List getSchooldaysForCurrentSemester() { + final currentSemester = _schoolCalendarManager.getCurrentSchoolSemester(); + if (currentSemester == null) { + return []; + } + + final allSchooldays = _schoolCalendarManager.schooldays.value; + final semesterStart = currentSemester.startDate.toLocal(); + final semesterEnd = currentSemester.endDate.toLocal(); + + return allSchooldays.where((schoolday) { + final schooldayDate = schoolday.schoolday.toLocal(); + final startDate = DateTime( + semesterStart.year, + semesterStart.month, + semesterStart.day, + ); + final endDate = DateTime( + semesterEnd.year, + semesterEnd.month, + semesterEnd.day, + ); + final dayDate = DateTime( + schooldayDate.year, + schooldayDate.month, + schooldayDate.day, + ); + return (dayDate.isAfter(startDate) || dayDate == startDate) && + (dayDate.isBefore(endDate) || dayDate == endDate); + }).toList()..sort((a, b) => a.schoolday.compareTo(b.schoolday)); + } + + /// Counts pupils with specialNeeds for a given schoolday + /// Only counts pupils where pupilSince <= schoolday date + int getSpecialNeedsCountForSchoolday(Schoolday schoolday) { + final pupils = _pupilManager.allPupils; + final schooldayDate = schoolday.schoolday.toLocal(); + final dayDate = DateTime( + schooldayDate.year, + schooldayDate.month, + schooldayDate.day, + ); + + return pupils.where((pupil) { + final pupilSince = pupil.pupilSince.toLocal(); + final sinceDate = DateTime( + pupilSince.year, + pupilSince.month, + pupilSince.day, + ); + // Pupil must be enrolled on or before this schoolday + if (sinceDate.isAfter(dayDate)) { + return false; + } + // Pupil must have specialNeeds + return pupil.specialNeeds != null && pupil.specialNeeds!.isNotEmpty; + }).length; + } + + /// Counts pupils with migrationSupportEnds for a given schoolday + /// Only counts pupils where pupilSince <= schoolday date + /// and migrationSupportEnds is on or after the schoolday + int getMigrationSupportCountForSchoolday(Schoolday schoolday) { + final pupils = _pupilManager.allPupils; + final schooldayDate = schoolday.schoolday.toLocal(); + final dayDate = DateTime( + schooldayDate.year, + schooldayDate.month, + schooldayDate.day, + ); + + return pupils.where((pupil) { + final pupilSince = pupil.pupilSince.toLocal(); + final sinceDate = DateTime( + pupilSince.year, + pupilSince.month, + pupilSince.day, + ); + // Pupil must be enrolled on or before this schoolday + if (sinceDate.isAfter(dayDate)) { + return false; + } + + // Pupil must have migrationSupportEnds + if (pupil.migrationSupportEnds == null) { + return false; + } + + // Check if migrationSupportEnds falls on a valid schoolday + final supportEndsDate = pupil.migrationSupportEnds!.toLocal(); + final supportDate = DateTime( + supportEndsDate.year, + supportEndsDate.month, + supportEndsDate.day, + ); + return dayDate.isBefore(supportDate) || dayDate == supportDate; + }).length; + } + + /// Counts new pupils enrolled during the semester up to a given schoolday + /// Cumulative count starting from 0 on the first semester day + int getNewPupilsCountForSchoolday( + Schoolday schoolday, + List allSchooldays, + ) { + final currentSemester = _schoolCalendarManager.getCurrentSchoolSemester(); + if (currentSemester == null) { + return 0; + } + + // First day of semester should always be 0 + if (allSchooldays.isNotEmpty) { + final firstDay = allSchooldays.first.schoolday.toLocal(); + final currentDay = schoolday.schoolday.toLocal(); + final firstDayDate = DateTime( + firstDay.year, + firstDay.month, + firstDay.day, + ); + final currentDayDate = DateTime( + currentDay.year, + currentDay.month, + currentDay.day, + ); + if (firstDayDate == currentDayDate) { + return 0; + } + } + + final pupils = _pupilManager.allPupils; + final schooldayDate = schoolday.schoolday.toLocal(); + final dayDate = DateTime( + schooldayDate.year, + schooldayDate.month, + schooldayDate.day, + ); + final semesterStart = currentSemester.startDate.toLocal(); + final semesterStartDate = DateTime( + semesterStart.year, + semesterStart.month, + semesterStart.day, + ); + + return pupils.where((pupil) { + final pupilSince = pupil.pupilSince.toLocal(); + final sinceDate = DateTime( + pupilSince.year, + pupilSince.month, + pupilSince.day, + ); + // Pupil must be enrolled on or before this schoolday + // AND pupilSince must be within the semester (on or after semester start) + return (sinceDate.isBefore(dayDate) || sinceDate == dayDate) && + (sinceDate.isAfter(semesterStartDate) || + sinceDate == semesterStartDate); + }).length; + } + + /// Counts pupils with support level 3 (no special needs) for a given schoolday + /// Only counts pupils where pupilSince <= schoolday date + /// and latestSupportLevel.createdAt <= schoolday date + int getSupportLevel3CountForSchoolday(Schoolday schoolday) { + final pupils = _pupilManager.allPupils; + final schooldayDate = schoolday.schoolday.toLocal(); + final dayDate = DateTime( + schooldayDate.year, + schooldayDate.month, + schooldayDate.day, + ); + + return pupils.where((pupil) { + final pupilSince = pupil.pupilSince.toLocal(); + final sinceDate = DateTime( + pupilSince.year, + pupilSince.month, + pupilSince.day, + ); + // Pupil must be enrolled on or before this schoolday + if (sinceDate.isAfter(dayDate)) { + return false; + } + + // Pupil must NOT have specialNeeds + if (pupil.specialNeeds != null && pupil.specialNeeds!.isNotEmpty) { + return false; + } + + // Pupil must have latestSupportLevel with level == 3 + final latestSupportLevel = pupil.latestSupportLevel; + if (latestSupportLevel == null || latestSupportLevel.level != 3) { + return false; + } + + // latestSupportLevel.createdAt must be equal to or before the schoolday + final supportCreatedAt = latestSupportLevel.createdAt.toLocal(); + final supportDate = DateTime( + supportCreatedAt.year, + supportCreatedAt.month, + supportCreatedAt.day, + ); + if (supportDate.isAfter(dayDate)) { + return false; + } + + return true; + }).length; + } + + /// Counts pupils without specialNeeds, migrationSupport, or support level 3 for a given schoolday + /// Only counts pupils where pupilSince <= schoolday date + int getRegularPupilsCountForSchoolday(Schoolday schoolday) { + final pupils = _pupilManager.allPupils; + final schooldayDate = schoolday.schoolday.toLocal(); + final dayDate = DateTime( + schooldayDate.year, + schooldayDate.month, + schooldayDate.day, + ); + + return pupils.where((pupil) { + final pupilSince = pupil.pupilSince.toLocal(); + final sinceDate = DateTime( + pupilSince.year, + pupilSince.month, + pupilSince.day, + ); + // Pupil must be enrolled on or before this schoolday + if (sinceDate.isAfter(dayDate)) { + return false; + } + + // Pupil must NOT have specialNeeds + if (pupil.specialNeeds != null && pupil.specialNeeds!.isNotEmpty) { + return false; + } + + // Pupil must NOT have active migrationSupportEnds + // (migrationSupportEnds is null OR it's before this schoolday) + if (pupil.migrationSupportEnds != null) { + final supportEndsDate = pupil.migrationSupportEnds!.toLocal(); + final supportDate = DateTime( + supportEndsDate.year, + supportEndsDate.month, + supportEndsDate.day, + ); + // If migrationSupportEnds is on or after this schoolday, pupil has migration support + if (dayDate.isBefore(supportDate) || dayDate == supportDate) { + return false; + } + } + + // Pupil must NOT have support level 3 (no special needs) + final latestSupportLevel = pupil.latestSupportLevel; + if (latestSupportLevel != null && latestSupportLevel.level == 3) { + final supportCreatedAt = latestSupportLevel.createdAt.toLocal(); + final supportDate = DateTime( + supportCreatedAt.year, + supportCreatedAt.month, + supportCreatedAt.day, + ); + // If support level 3 was created on or before this schoolday, exclude from regular + if (supportDate.isBefore(dayDate) || supportDate == dayDate) { + return false; + } + } + + return true; + }).length; + } + + /// Gets data for the chart: schooldays with their counts + Map< + DateTime, + ({ + int specialNeeds, + int migrationSupport, + int supportLevel3, + int regularPupils, + int newPupils, + }) + > + getChartData() { + final schooldays = getSchooldaysForCurrentSemester(); + final Map< + DateTime, + ({ + int specialNeeds, + int migrationSupport, + int supportLevel3, + int regularPupils, + int newPupils, + }) + > + data = {}; + + for (final schoolday in schooldays) { + data[schoolday.schoolday] = ( + specialNeeds: getSpecialNeedsCountForSchoolday(schoolday), + migrationSupport: getMigrationSupportCountForSchoolday(schoolday), + supportLevel3: getSupportLevel3CountForSchoolday(schoolday), + regularPupils: getRegularPupilsCountForSchoolday(schoolday), + newPupils: getNewPupilsCountForSchoolday(schoolday, schooldays), + ); + } + + return data; + } + + @override + Widget build(BuildContext context) { + final chartData = getChartData(); + final schooldays = getSchooldaysForCurrentSemester(); + + if (schooldays.isEmpty) { + return Scaffold( + backgroundColor: const Color(0xfff2f2f7), + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: const Color.fromRGBO(74, 76, 161, 1), + centerTitle: true, + title: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.bar_chart_rounded, size: 25, color: Colors.white), + SizedBox(width: 10), + Text( + 'Statistik Diagramm', + style: TextStyle(color: Colors.white, fontSize: 20), + ), + ], + ), + ), + body: const Center(child: Text('Kein Schulhalbjahr gefunden')), + ); + } + + return ChartPage(chartData: chartData, schooldays: schooldays); + } +} diff --git a/school_data_hub_flutter/lib/features/statistics/statistics_page/controller/statistics.dart b/school_data_hub_flutter/lib/features/statistics/statistics_page/controller/statistics.dart index 877da66e..9f3c3579 100644 --- a/school_data_hub_flutter/lib/features/statistics/statistics_page/controller/statistics.dart +++ b/school_data_hub_flutter/lib/features/statistics/statistics_page/controller/statistics.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/pupil_helper_functions.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/pupil_manager.dart'; diff --git a/school_data_hub_flutter/lib/features/statistics/statistics_page/statistics_page.dart b/school_data_hub_flutter/lib/features/statistics/statistics_page/statistics_page.dart index 887b431d..43065b2c 100644 --- a/school_data_hub_flutter/lib/features/statistics/statistics_page/statistics_page.dart +++ b/school_data_hub_flutter/lib/features/statistics/statistics_page/statistics_page.dart @@ -3,6 +3,7 @@ import 'package:gap/gap.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; // ignore: directives_ordering +import 'package:school_data_hub_flutter/features/statistics/chart_page/chart_page_controller.dart'; import 'package:school_data_hub_flutter/features/statistics/statistics_page/controller/statistics.dart'; import 'package:school_data_hub_flutter/features/statistics/statistics_page/list_tiles/enrollment_list_tiles.dart'; import 'package:school_data_hub_flutter/features/statistics/statistics_page/list_tiles/group_list_tiles.dart'; @@ -37,8 +38,22 @@ class StatisticsPage extends StatelessWidget { child: Column( children: [ const Gap(15), - const Row( - children: [Text('Schulzahlen', style: AppStyles.title)], + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Schulzahlen', style: AppStyles.title), + IconButton( + icon: const Icon(Icons.show_chart), + tooltip: 'Diagramm anzeigen', + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => const ChartPageController(), + ), + ); + }, + ), + ], ), const Gap(10), Expanded( diff --git a/school_data_hub_flutter/lib/features/timetable/presentation/new_lesson_group_page/new_lesson_group_page.dart b/school_data_hub_flutter/lib/features/timetable/presentation/new_lesson_group_page/new_lesson_group_page.dart index b0fea3ec..d915ce4d 100644 --- a/school_data_hub_flutter/lib/features/timetable/presentation/new_lesson_group_page/new_lesson_group_page.dart +++ b/school_data_hub_flutter/lib/features/timetable/presentation/new_lesson_group_page/new_lesson_group_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/features/timetable/domain/timetable_manager.dart'; @@ -160,7 +160,7 @@ class NewLessonGroupPage extends WatchingWidget { return; } - final now = DateTime.now().toUtcForServer(); + final now = DateTime.now().formatToUtcForServer(); final lessonGroupData = LessonGroup( id: lessonGroup?.id, publicId: @@ -221,97 +221,89 @@ class NewLessonGroupPage extends WatchingWidget { } }, onCancel: () => Navigator.of(context).pop(), - onDelete: - _isEditing - ? () { - if (lessonGroup?.id == null) return; + onDelete: _isEditing + ? () { + if (lessonGroup?.id == null) return; - // Check if the lesson group is used in any scheduled lessons - final scheduledLessons = - timetableManager.scheduledLessons.value - .where( - (lesson) => - lesson.lessonGroupId == - lessonGroup!.id, - ) - .toList(); - - if (scheduledLessons.isNotEmpty) { - showDialog( - context: context, - builder: - (context) => AlertDialog( - title: const Text( - 'Klasse kann nicht gelöscht werden', - ), - content: Text( - 'Diese Klasse wird in ${scheduledLessons.length} geplanten Stunden verwendet und kann nicht gelöscht werden.', - ), - actions: [ - TextButton( - onPressed: - () => - Navigator.of( - context, - ).pop(), - child: const Text('OK'), - ), - ], - ), - ); - return; - } + // Check if the lesson group is used in any scheduled lessons + final scheduledLessons = timetableManager + .scheduledLessons + .value + .where( + (lesson) => + lesson.lessonGroupId == lessonGroup!.id, + ) + .toList(); + if (scheduledLessons.isNotEmpty) { showDialog( context: context, - builder: - (context) => AlertDialog( - title: const Text('Klasse löschen'), - content: Text( - 'Sind Sie sicher, dass Sie die Klasse "${lessonGroup!.name}" löschen möchten?\n\n' - 'Diese Aktion kann nicht rückgängig gemacht werden.', - ), - actions: [ - TextButton( - onPressed: - () => - Navigator.of(context).pop(), - child: const Text('Abbrechen'), - ), - TextButton( - onPressed: () { - timetableManager - .removeLessonGroup( - lessonGroup!.id!, - ); - Navigator.of( - context, - ).pop(); // Close dialog - Navigator.of( - context, - ).pop(); // Close page + builder: (context) => AlertDialog( + title: const Text( + 'Klasse kann nicht gelöscht werden', + ), + content: Text( + 'Diese Klasse wird in ${scheduledLessons.length} geplanten Stunden verwendet und kann nicht gelöscht werden.', + ), + actions: [ + TextButton( + onPressed: () => + Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + return; + } + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Klasse löschen'), + content: Text( + 'Sind Sie sicher, dass Sie die Klasse "${lessonGroup!.name}" löschen möchten?\n\n' + 'Diese Aktion kann nicht rückgängig gemacht werden.', + ), + actions: [ + TextButton( + onPressed: () => + Navigator.of(context).pop(), + child: const Text('Abbrechen'), + ), + TextButton( + onPressed: () { + timetableManager.removeLessonGroup( + lessonGroup!.id!, + ); + Navigator.of( + context, + ).pop(); // Close dialog + Navigator.of( + context, + ).pop(); // Close page - ScaffoldMessenger.of( - context, - ).showSnackBar( - SnackBar( - content: Text( - 'Klasse "${lessonGroup!.name}" wurde gelöscht', - ), - backgroundColor: Colors.red, - ), - ); - }, - style: TextButton.styleFrom( - foregroundColor: Colors.red, + ScaffoldMessenger.of( + context, + ).showSnackBar( + SnackBar( + content: Text( + 'Klasse "${lessonGroup!.name}" wurde gelöscht', ), - child: const Text('Löschen'), + backgroundColor: Colors.red, ), - ], + ); + }, + style: TextButton.styleFrom( + foregroundColor: Colors.red, ), - ); - } - : null, + child: const Text('Löschen'), + ), + ], + ), + ); + } + : null, ), ], ), diff --git a/school_data_hub_flutter/lib/features/timetable/presentation/new_scheduled_lesson_page/new_scheduled_lesson_page.dart b/school_data_hub_flutter/lib/features/timetable/presentation/new_scheduled_lesson_page/new_scheduled_lesson_page.dart index 28890e10..2e3e2ade 100644 --- a/school_data_hub_flutter/lib/features/timetable/presentation/new_scheduled_lesson_page/new_scheduled_lesson_page.dart +++ b/school_data_hub_flutter/lib/features/timetable/presentation/new_scheduled_lesson_page/new_scheduled_lesson_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; @@ -44,10 +44,9 @@ class NewScheduledLessonPage extends WatchingWidget { // Create ValueListenable for state management final selectedSubject = createOnce>(() { if (_isEditing) { - final editingLesson = - timetableManager.scheduledLessons.value - .where((lesson) => lesson.id == editingLessonId) - .firstOrNull; + final editingLesson = timetableManager.scheduledLessons.value + .where((lesson) => lesson.id == editingLessonId) + .firstOrNull; if (editingLesson != null) { return ValueNotifier( timetableManager.getSubjectById(editingLesson.subjectId), @@ -59,10 +58,9 @@ class NewScheduledLessonPage extends WatchingWidget { final selectedSlot = createOnce>(() { if (_isEditing) { - final editingLesson = - timetableManager.scheduledLessons.value - .where((lesson) => lesson.id == editingLessonId) - .firstOrNull; + final editingLesson = timetableManager.scheduledLessons.value + .where((lesson) => lesson.id == editingLessonId) + .firstOrNull; if (editingLesson != null) { return ValueNotifier( timetableManager.getTimetableSlotById(editingLesson.scheduledAtId), @@ -78,10 +76,9 @@ class NewScheduledLessonPage extends WatchingWidget { final selectedClassroom = createOnce>(() { if (_isEditing) { - final editingLesson = - timetableManager.scheduledLessons.value - .where((lesson) => lesson.id == editingLessonId) - .firstOrNull; + final editingLesson = timetableManager.scheduledLessons.value + .where((lesson) => lesson.id == editingLessonId) + .firstOrNull; if (editingLesson != null) { return ValueNotifier( timetableManager.getClassroomById(editingLesson.roomId), @@ -93,10 +90,9 @@ class NewScheduledLessonPage extends WatchingWidget { final selectedLessonGroup = createOnce>(() { if (_isEditing) { - final editingLesson = - timetableManager.scheduledLessons.value - .where((lesson) => lesson.id == editingLessonId) - .firstOrNull; + final editingLesson = timetableManager.scheduledLessons.value + .where((lesson) => lesson.id == editingLessonId) + .firstOrNull; if (editingLesson != null) { return ValueNotifier( timetableManager.getLessonGroupById(editingLesson.lessonGroupId), @@ -119,10 +115,9 @@ class NewScheduledLessonPage extends WatchingWidget { // Initialize lesson ID controller if editing if (_isEditing) { - final editingLesson = - timetableManager.scheduledLessons.value - .where((lesson) => lesson.id == editingLessonId) - .firstOrNull; + final editingLesson = timetableManager.scheduledLessons.value + .where((lesson) => lesson.id == editingLessonId) + .firstOrNull; if (editingLesson != null) { lessonIdController.text = editingLesson.lessonId; } @@ -196,14 +191,10 @@ class NewScheduledLessonPage extends WatchingWidget { selectedClassroom.value = null; } }, - hasLessonGroupConflict: - (group) => - _hasLessonGroupConflict(group, selectedSlotValue), - hasClassroomConflict: - (classroom) => _hasClassroomConflict( - classroom, - selectedSlotValue, - ), + hasLessonGroupConflict: (group) => + _hasLessonGroupConflict(group, selectedSlotValue), + hasClassroomConflict: (classroom) => + _hasClassroomConflict(classroom, selectedSlotValue), ), const Gap(20), @@ -213,11 +204,8 @@ class NewScheduledLessonPage extends WatchingWidget { onClassroomChanged: (classroom) { selectedClassroom.value = classroom; }, - hasClassroomConflict: - (classroom) => _hasClassroomConflict( - classroom, - selectedSlotValue, - ), + hasClassroomConflict: (classroom) => + _hasClassroomConflict(classroom, selectedSlotValue), ), const Gap(20), @@ -227,9 +215,8 @@ class NewScheduledLessonPage extends WatchingWidget { onLessonGroupChanged: (group) { selectedLessonGroup.value = group; }, - hasLessonGroupConflict: - (group) => - _hasLessonGroupConflict(group, selectedSlotValue), + hasLessonGroupConflict: (group) => + _hasLessonGroupConflict(group, selectedSlotValue), ), const Gap(20), @@ -272,15 +259,14 @@ class NewScheduledLessonPage extends WatchingWidget { return; } - final now = DateTime.now().toUtcForServer(); + final now = DateTime.now().formatToUtcForServer(); if (_isEditing) { - final editingLesson = - timetableManager.scheduledLessons.value - .where( - (lesson) => lesson.id == editingLessonId, - ) - .firstOrNull; + final editingLesson = timetableManager + .scheduledLessons + .value + .where((lesson) => lesson.id == editingLessonId) + .firstOrNull; if (editingLesson != null) { // Update existing lesson @@ -336,9 +322,8 @@ class NewScheduledLessonPage extends WatchingWidget { lessonGroup: selectedLessonGroupValue, timetableSlotOrder: nextAvailableOrder, mainTeacherId: selectedTeachersValue.first.id!, - createdBy: - di() - .userName!, // TODO: Get actual user + createdBy: di() + .userName!, // TODO: Get actual user createdAt: now, ); @@ -357,66 +342,61 @@ class NewScheduledLessonPage extends WatchingWidget { Navigator.of(context).pop(); }, onCancel: () => Navigator.of(context).pop(), - onDelete: - _isEditing - ? () { - final editingLesson = - timetableManager.scheduledLessons.value - .where( - (lesson) => - lesson.id == editingLessonId, - ) - .firstOrNull; - - if (editingLesson?.id == null) return; - - showDialog( - context: context, - builder: - (context) => AlertDialog( - title: const Text('Stunde löschen'), - content: const Text( - 'Sind Sie sicher, dass Sie diese Stunde löschen möchten?', - ), - actions: [ - TextButton( - onPressed: - () => - Navigator.of(context).pop(), - child: const Text('Abbrechen'), - ), - TextButton( - onPressed: () { - timetableManager - .removeScheduledLesson( - editingLesson!.id!, - ); - Navigator.of( - context, - ).pop(); // Close dialog - Navigator.of( - context, - ).pop(); // Close page - - ScaffoldMessenger.of( - context, - ).showSnackBar( - const SnackBar( - content: Text( - 'Stunde erfolgreich gelöscht', - ), - backgroundColor: - Colors.orange, - ), - ); - }, - child: const Text('Löschen'), + onDelete: _isEditing + ? () { + final editingLesson = timetableManager + .scheduledLessons + .value + .where( + (lesson) => lesson.id == editingLessonId, + ) + .firstOrNull; + + if (editingLesson?.id == null) return; + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Stunde löschen'), + content: const Text( + 'Sind Sie sicher, dass Sie diese Stunde löschen möchten?', + ), + actions: [ + TextButton( + onPressed: () => + Navigator.of(context).pop(), + child: const Text('Abbrechen'), + ), + TextButton( + onPressed: () { + timetableManager.removeScheduledLesson( + editingLesson!.id!, + ); + Navigator.of( + context, + ).pop(); // Close dialog + Navigator.of( + context, + ).pop(); // Close page + + ScaffoldMessenger.of( + context, + ).showSnackBar( + const SnackBar( + content: Text( + 'Stunde erfolgreich gelöscht', + ), + backgroundColor: Colors.orange, ), - ], - ), - ); - } - : null, + ); + }, + child: const Text('Löschen'), + ), + ], + ), + ); + } + : null, ), ], ), diff --git a/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/new_timetable_page.dart b/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/new_timetable_page.dart index 26e2e122..09515cfc 100644 --- a/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/new_timetable_page.dart +++ b/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/new_timetable_page.dart @@ -2,7 +2,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; @@ -52,7 +52,7 @@ class NewTimetablePage extends WatchingWidget { final startDateController = createOnce(() { final controller = TextEditingController(); if (_isEditing && timetable != null) { - controller.text = timetable!.startsAt.formatForUser(); + controller.text = timetable!.startsAt.formatDateForUser(); } return controller; }); @@ -60,7 +60,7 @@ class NewTimetablePage extends WatchingWidget { final endDateController = createOnce(() { final controller = TextEditingController(); if (_isEditing && timetable != null && timetable!.endsAt != null) { - controller.text = timetable!.endsAt!.formatForUser(); + controller.text = timetable!.endsAt!.formatDateForUser(); } return controller; }); @@ -214,16 +214,16 @@ class NewTimetablePage extends WatchingWidget { final newTimetable = Timetable( id: _isEditing ? timetable!.id : null, active: _isEditing ? timetable!.active : true, - startsAt: startDate.toUtcForServer(), - endsAt: endDate?.toUtcForServer(), + startsAt: startDate.formatToUtcForServer(), + endsAt: endDate?.formatToUtcForServer(), name: name, schoolSemesterId: selectedSemester.value!.id!, - createdBy: - _isEditing ? timetable!.createdBy : userName, - createdAt: - _isEditing - ? timetable!.createdAt - : DateTime.now().toUtcForServer(), + createdBy: _isEditing + ? timetable!.createdBy + : userName, + createdAt: _isEditing + ? timetable!.createdAt + : DateTime.now().formatToUtcForServer(), ); if (_isEditing) { diff --git a/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/widgets/end_date_field.dart b/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/widgets/end_date_field.dart index 74cc46fb..4f582daf 100644 --- a/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/widgets/end_date_field.dart +++ b/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/widgets/end_date_field.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/features/school_calendar/domain/school_calendar_manager.dart'; import 'package:watch_it/watch_it.dart'; @@ -35,7 +35,7 @@ class EndDateField extends StatelessWidget { ); if (pickedDate != null) { selectedDate.value = pickedDate; - controller.text = pickedDate.formatForUser(); + controller.text = pickedDate.formatDateForUser(); } }, child: Container( @@ -53,10 +53,9 @@ class EndDateField extends StatelessWidget { ? controller.text : 'Bitte auswählen', style: TextStyle( - color: - controller.text.isNotEmpty - ? Colors.black - : Colors.grey.shade600, + color: controller.text.isNotEmpty + ? Colors.black + : Colors.grey.shade600, fontSize: 16, ), ), diff --git a/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/widgets/start_date_field.dart b/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/widgets/start_date_field.dart index 28ced52d..fa60545f 100644 --- a/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/widgets/start_date_field.dart +++ b/school_data_hub_flutter/lib/features/timetable/presentation/new_timetable_page/widgets/start_date_field.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/features/school_calendar/domain/school_calendar_manager.dart'; import 'package:watch_it/watch_it.dart'; @@ -35,7 +35,7 @@ class StartDateField extends StatelessWidget { ); if (pickedDate != null) { selectedDate.value = pickedDate; - controller.text = pickedDate.formatForUser(); + controller.text = pickedDate.formatDateForUser(); } }, child: Container( @@ -53,10 +53,9 @@ class StartDateField extends StatelessWidget { ? controller.text : 'Bitte auswählen', style: TextStyle( - color: - controller.text.isNotEmpty - ? Colors.black - : Colors.grey.shade600, + color: controller.text.isNotEmpty + ? Colors.black + : Colors.grey.shade600, fontSize: 16, ), ), diff --git a/school_data_hub_flutter/lib/features/user/domain/user_manager.dart b/school_data_hub_flutter/lib/features/user/domain/user_manager.dart index da808dac..defe82f4 100644 --- a/school_data_hub_flutter/lib/features/user/domain/user_manager.dart +++ b/school_data_hub_flutter/lib/features/user/domain/user_manager.dart @@ -45,11 +45,10 @@ class UserManager { required String fullName, required String password, required String email, - + required String matrixUserId, required int timeUnits, required int reliefTimeUnits, required int credit, - required String contact, required List scopeNames, required Role role, required bool isTester, @@ -60,6 +59,7 @@ class UserManager { userName: userName, fullName: fullName, email: email, + matrixUserId: matrixUserId, password: password, role: role, timeUnits: timeUnits, @@ -147,8 +147,9 @@ class UserManager { } void removeUser(User user) { - _users.value = - _users.value.where((element) => element.id != user.id).toList(); + _users.value = _users.value + .where((element) => element.id != user.id) + .toList(); } // void updateUser(User user) { @@ -162,8 +163,9 @@ class UserManager { } void removeUsers(List users) { - _users.value = - _users.value.where((element) => !users.contains(element)).toList(); + _users.value = _users.value + .where((element) => !users.contains(element)) + .toList(); } void addUsers(List users) { diff --git a/school_data_hub_flutter/lib/features/user/presentation/create_user/create_user_page.dart b/school_data_hub_flutter/lib/features/user/presentation/create_user/create_user_page.dart index 11c57004..c1944c3b 100644 --- a/school_data_hub_flutter/lib/features/user/presentation/create_user/create_user_page.dart +++ b/school_data_hub_flutter/lib/features/user/presentation/create_user/create_user_page.dart @@ -6,6 +6,7 @@ import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/information_dialog.dart'; import 'package:school_data_hub_flutter/features/user/domain/user_manager.dart'; +import 'package:school_data_hub_flutter/features/user/presentation/create_user/widgets/scope_names_selector.dart'; import 'package:school_data_hub_flutter/features/user/presentation/widgets/roles_dropdown.dart'; import 'package:watch_it/watch_it.dart'; @@ -28,12 +29,18 @@ class CreateUserPage extends WatchingWidget { final TextEditingController repeatPasswordController = createOnce( () => TextEditingController(), ); - final TextEditingController contactController = createOnce( + final TextEditingController emailController = createOnce( + () => TextEditingController(), + ); + final TextEditingController matrixIdController = createOnce( () => TextEditingController(), ); final setAsAdmin = createOnce(() => ValueNotifier(false)); final setAsTester = createOnce(() => ValueNotifier(false)); final role = createOnce(() => ValueNotifier(Role.notAssigned)); + final scopeNames = createOnce(() => ValueNotifier>([])); + final watchedScopeNames = watch(scopeNames).value; + void changeRole(Role? newRole) { role.value = newRole!; } @@ -72,270 +79,306 @@ class CreateUserPage extends WatchingWidget { child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 800), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - children: [ - const Text( - 'Name:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + const Text( + 'Name:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), - ), - const Gap(5), - Expanded( - child: TextField( - minLines: 1, - maxLines: 1, - controller: fullNameController, - decoration: AppStyles.textFieldDecoration( - labelText: 'Name', + const Gap(5), + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: fullNameController, + decoration: AppStyles.textFieldDecoration( + labelText: 'Name', + ), ), ), - ), - ], - ), - const Gap(20), - Row( - crossAxisAlignment: CrossAxisAlignment.center, // Add this - children: [ - const Text( - 'Kürzel', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + ], + ), + const Gap(20), + Row( + crossAxisAlignment: CrossAxisAlignment.center, // Add this + children: [ + const Text( + 'Kürzel', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), - ), - const Gap(5), - SizedBox( - width: 70, - child: TextField( - minLines: 1, - maxLines: 1, - controller: userNameController, - inputFormatters: [LengthLimitingTextInputFormatter(3)], - decoration: AppStyles.textFieldDecoration( - labelText: 'Kürzel', + const Gap(5), + SizedBox( + width: 70, + child: TextField( + minLines: 1, + maxLines: 1, + controller: userNameController, + inputFormatters: [ + LengthLimitingTextInputFormatter(3), + ], + decoration: AppStyles.textFieldDecoration( + labelText: 'Kürzel', + ), ), ), - ), - const Gap(15), - const Text( - 'Ist Admin:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + const Gap(15), + const Text( + 'Ist Admin:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), - ), - Checkbox( - value: watchedSetAsAdmin, - onChanged: (bool? newValue) { - setAsAdmin.value = newValue ?? false; - }, - ), - const Text( - 'Ist Tester*in:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + Checkbox( + value: watchedSetAsAdmin, + onChanged: (bool? newValue) { + setAsAdmin.value = newValue ?? false; + }, ), - ), - Checkbox( - value: watchedSetAsTester, - onChanged: (bool? newValue) { - setAsTester.value = newValue ?? false; - }, - ), - const Gap(5), - ], - ), - const Gap(20), - Row( - children: [ - const Text( - 'Passwort:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + const Text( + 'Ist Tester*in:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), - ), - const Gap(5), - Expanded( - child: TextField( - minLines: 1, - maxLines: 1, - controller: passwordController, - decoration: AppStyles.textFieldDecoration( - labelText: 'Passwort', + Checkbox( + value: watchedSetAsTester, + onChanged: (bool? newValue) { + setAsTester.value = newValue ?? false; + }, + ), + const Gap(5), + ], + ), + const Gap(20), + Row( + children: [ + const Text( + 'Passwort:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), ), - ), - ], - ), - const Gap(20), - Row( - children: [ - const Text( - 'Passwort wiederholen:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + const Gap(5), + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: passwordController, + decoration: AppStyles.textFieldDecoration( + labelText: 'Passwort', + ), + ), ), - ), - const Gap(5), - Expanded( - child: TextField( - minLines: 1, - maxLines: 1, - controller: repeatPasswordController, - decoration: AppStyles.textFieldDecoration( - labelText: 'Passwort wiederholen', + ], + ), + const Gap(20), + Row( + children: [ + const Text( + 'Passwort wiederholen:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), ), - ), - ], - ), - Row( - children: [ - RolesDropdown( - selectedRole: watchedRole, - changeRole: changeRole, - ), - ], - ), - const Gap(20), - Row( - children: [ - const Text( - 'Kontakt:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + const Gap(5), + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: repeatPasswordController, + decoration: AppStyles.textFieldDecoration( + labelText: 'Passwort wiederholen', + ), + ), ), - ), - const Gap(5), - Expanded( - child: TextField( - minLines: 1, - maxLines: 1, - controller: contactController, - decoration: AppStyles.textFieldDecoration( - labelText: 'Kontakt', + ], + ), + Row( + children: [ + RolesDropdown( + selectedRole: watchedRole, + changeRole: changeRole, + ), + ], + ), + const Gap(20), + ScopeNamesSelector(scopeNames: scopeNames), + const Gap(20), + + Row( + children: [ + const Text( + 'Matrix-ID:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), ), - ), - ], - ), - const Gap(20), - Row( - children: [ - const Text( - 'Guthaben:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + const Gap(5), + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: matrixIdController, + decoration: AppStyles.textFieldDecoration( + labelText: 'Matrix-ID', + ), + ), ), - ), - const Gap(5), - Expanded( - child: TextField( - minLines: 1, - maxLines: 1, - controller: creditController, - decoration: AppStyles.textFieldDecoration( - labelText: 'Guthaben', + ], + ), + const Gap(20), + + Row( + children: [ + const Text( + 'E-Mail:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), ), - ), - const Gap(15), - const Text( - 'Stunden:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + const Gap(5), + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: emailController, + decoration: AppStyles.textFieldDecoration( + labelText: 'E-Mail', + ), + ), ), - ), - const Gap(5), - Expanded( - child: TextField( - minLines: 1, - maxLines: 1, - controller: timeUnitsController, - decoration: AppStyles.textFieldDecoration( - labelText: 'Stunden', + ], + ), + const Gap(20), + Row( + children: [ + const Text( + 'Guthaben:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), ), - ), - const Text( - 'EntlastungsStunden:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + const Gap(5), + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: creditController, + decoration: AppStyles.textFieldDecoration( + labelText: 'Guthaben', + ), + ), ), - ), - const Gap(5), - Expanded( - child: TextField( - minLines: 1, - maxLines: 1, - controller: reliefTimeUnitsController, - decoration: AppStyles.textFieldDecoration( - labelText: 'EntlastungsStunden', + const Gap(15), + const Text( + 'Stunden:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), ), - ), - ], - ), - const Spacer(), - const Gap(15), - ElevatedButton( - style: AppStyles.successButtonStyle, - onPressed: () async { - if (passwordController.text != - repeatPasswordController.text) { - informationDialog( - context, - 'Passwörter stimmen nicht überein', - 'Bitte Passwort überprüfen', - ); - return; - } + const Gap(5), + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: timeUnitsController, + decoration: AppStyles.textFieldDecoration( + labelText: 'Stunden', + ), + ), + ), + const Text( + 'EntlastungsStunden:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Gap(5), + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: reliefTimeUnitsController, + decoration: AppStyles.textFieldDecoration( + labelText: 'EntlastungsStunden', + ), + ), + ), + ], + ), + const Gap(20), + ElevatedButton( + style: AppStyles.successButtonStyle, + onPressed: () async { + if (passwordController.text != + repeatPasswordController.text) { + informationDialog( + context, + 'Passwörter stimmen nicht überein', + 'Bitte Passwort überprüfen', + ); + return; + } - await _userManager.createUser( - userName: userNameController.text, - fullName: fullNameController.text, - email: contactController.text, - password: passwordController.text, - role: watchedSetAsAdmin ? Role.admin : watchedRole, - timeUnits: int.tryParse(timeUnitsController.text) ?? 0, - reliefTimeUnits: - int.tryParse(reliefTimeUnitsController.text) ?? 0, - credit: int.tryParse(creditController.text) ?? 0, - contact: contactController.text, - isTester: setAsTester.value, - scopeNames: watchedSetAsAdmin ? ['admin'] : ['standard'], - ); + await _userManager.createUser( + userName: userNameController.text, + fullName: fullNameController.text, + matrixUserId: matrixIdController.text, + email: emailController.text, + password: passwordController.text, + role: watchedSetAsAdmin ? Role.admin : watchedRole, + timeUnits: int.tryParse(timeUnitsController.text) ?? 0, + reliefTimeUnits: + int.tryParse(reliefTimeUnitsController.text) ?? 0, + credit: int.tryParse(creditController.text) ?? 0, + + isTester: setAsTester.value, + scopeNames: watchedScopeNames.isNotEmpty + ? watchedScopeNames + : (watchedSetAsAdmin ? ['admin'] : ['standard']), + ); - if (context.mounted) { + if (context.mounted) { + Navigator.pop(context); + } + }, + child: const Text( + 'SENDEN', + style: AppStyles.buttonTextStyle, + ), + ), + const Gap(15), + ElevatedButton( + style: AppStyles.cancelButtonStyle, + onPressed: () { Navigator.pop(context); - } - }, - child: const Text('SENDEN', style: AppStyles.buttonTextStyle), - ), - const Gap(15), - ElevatedButton( - style: AppStyles.cancelButtonStyle, - onPressed: () { - Navigator.pop(context); - }, - child: const Text( - 'ABBRECHEN', - style: AppStyles.buttonTextStyle, + }, + child: const Text( + 'ABBRECHEN', + style: AppStyles.buttonTextStyle, + ), ), - ), - ], + ], + ), ), ), ), diff --git a/school_data_hub_flutter/lib/features/user/presentation/create_user/widgets/scope_names_selector.dart b/school_data_hub_flutter/lib/features/user/presentation/create_user/widgets/scope_names_selector.dart new file mode 100644 index 00000000..518afe9e --- /dev/null +++ b/school_data_hub_flutter/lib/features/user/presentation/create_user/widgets/scope_names_selector.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:school_data_hub_flutter/common/theme/styles.dart'; +import 'package:watch_it/watch_it.dart'; + +class ScopeNamesSelector extends WatchingWidget { + final ValueNotifier> scopeNames; + + const ScopeNamesSelector({super.key, required this.scopeNames}); + + @override + Widget build(BuildContext context) { + final watchedScopeNames = watch(scopeNames).value; + final TextEditingController newScopeNameController = createOnce( + () => TextEditingController(), + ); + + void addScopeName() { + final newScopeName = + 'SchooldayEventsManagement.${newScopeNameController.text.trim()}'; + if (newScopeName.isNotEmpty && + !watchedScopeNames.contains(newScopeName)) { + scopeNames.value = [...watchedScopeNames, newScopeName]; + newScopeNameController.clear(); + } + } + + void removeScopeName(String scopeName) { + scopeNames.value = watchedScopeNames + .where((element) => element != scopeName) + .toList(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Scope Names:', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const Gap(5), + Row( + children: [ + Expanded( + child: TextField( + minLines: 1, + maxLines: 1, + controller: newScopeNameController, + decoration: AppStyles.textFieldDecoration( + labelText: 'Neue Scope Name hinzufügen', + ), + onSubmitted: (_) => addScopeName(), + ), + ), + const Gap(10), + ElevatedButton( + style: AppStyles.actionButtonStyle.copyWith( + minimumSize: WidgetStateProperty.all(const Size(0, 50)), + ), + onPressed: addScopeName, + child: const Text('HINZUFÜGEN', style: AppStyles.buttonTextStyle), + ), + ], + ), + if (watchedScopeNames.isNotEmpty) ...[ + const Gap(10), + Container( + constraints: const BoxConstraints(maxHeight: 200), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey, width: 1), + borderRadius: BorderRadius.circular(4), + ), + child: ListView.builder( + shrinkWrap: true, + padding: const EdgeInsets.all(8), + itemCount: watchedScopeNames.length, + itemBuilder: (context, index) { + final scopeName = watchedScopeNames[index]; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(4), + ), + child: Text( + scopeName, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const Gap(8), + InkWell( + onTap: () => removeScopeName(scopeName), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: BorderRadius.circular(4), + ), + child: const Icon( + Icons.close, + size: 18, + color: Colors.red, + ), + ), + ), + ], + ), + ); + }, + ), + ), + ], + ], + ); + } +} diff --git a/school_data_hub_flutter/lib/features/user/presentation/user_list/widgets/user_list_card.dart b/school_data_hub_flutter/lib/features/user/presentation/user_list/widgets/user_list_card.dart index bc0312fe..ac8fa469 100644 --- a/school_data_hub_flutter/lib/features/user/presentation/user_list/widgets/user_list_card.dart +++ b/school_data_hub_flutter/lib/features/user/presentation/user_list/widgets/user_list_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile_content.dart'; @@ -207,7 +207,7 @@ class UserListCard extends WatchingWidget { Text('User Info ID: ${user.userInfoId}'), const Gap(5), Text( - 'Erstellt: ${user.userInfo?.created != null ? user.userInfo!.created.formatForUser() : 'N/A'}', + 'Erstellt: ${user.userInfo?.created != null ? user.userInfo!.created.formatDateForUser() : 'N/A'}', ), const Gap(5), Text( diff --git a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart index cdaecc46..c4d63ed8 100644 --- a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart +++ b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/datetime_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; @@ -20,266 +20,302 @@ import 'package:school_data_hub_flutter/features/workbooks/domain/workbook_manag import 'package:watch_it/watch_it.dart'; class PupilWorkbookCard extends WatchingWidget { - const PupilWorkbookCard( - {required this.pupilWorkbook, required this.pupilId, super.key}); + const PupilWorkbookCard({ + required this.pupilWorkbook, + required this.pupilId, + super.key, + }); final PupilWorkbook pupilWorkbook; final int pupilId; void onChangedGrowthDropdown(int value) { - di() - .updatePupilWorkbook(pupilWorkbook: pupilWorkbook, score: value); + di().updatePupilWorkbook( + pupilWorkbook: pupilWorkbook, + score: value, + ); } @override Widget build(BuildContext context) { - final Workbook workbook = - di().getWorkbookByIsbn(pupilWorkbook.workbook!.isbn)!; + final Workbook workbook = di().getWorkbookByIsbn( + pupilWorkbook.workbook!.isbn, + )!; return ClipRRect( borderRadius: BorderRadius.circular(20), child: Card( - child: InkWell( - onLongPress: () async { - if (pupilWorkbook.createdBy != di().userName || - !di().isAdmin) { - informationDialog(context, 'Keine Berechtigung', - 'Arbeitshefte können nur von der eintragenden Person bearbeitet werden!'); - return; - } - final bool? result = await confirmationDialog( + child: InkWell( + onLongPress: () async { + if (pupilWorkbook.createdBy != di().userName || + !di().isAdmin) { + informationDialog( + context, + 'Keine Berechtigung', + 'Arbeitshefte können nur von der eintragenden Person bearbeitet werden!', + ); + return; + } + final bool? result = await confirmationDialog( context: context, title: 'Arbeitsheft löschen', - message: 'Arbeitsheft "${workbook.name}" wirklich löschen?'); - if (result == true) { - di() - .deletePupilWorkbook(pupilId, workbook.isbn); - } - }, - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Gap(5), - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - InkWell( - onTap: () async { - final File? file = await createImageFile(context); - if (file == null) return; - // TODO: Uncomment when API is ready - di().showSnackBar( - NotificationType.warning, 'Not implemented yet'); - // await di() - // .postWorkbookFile(file, workbook.isbn); - }, - onLongPress: (workbook.imageUrl == null) - ? () {} - : () async { - if (workbook.imageUrl == null) { - return; - } - final bool? result = await confirmationDialog( + message: 'Arbeitsheft "${workbook.name}" wirklich löschen?', + ); + if (result == true) { + di().deletePupilWorkbook( + pupilId, + workbook.isbn, + ); + } + }, + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(5), + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + InkWell( + onTap: () async { + final File? file = await createImageFile(context); + if (file == null) return; + // TODO: Uncomment when API is ready + di().showSnackBar( + NotificationType.warning, + 'Not implemented yet', + ); + // await di() + // .postWorkbookFile(file, workbook.isbn); + }, + onLongPress: (workbook.imageUrl == null) + ? () {} + : () async { + if (workbook.imageUrl == null) { + return; + } + final bool? result = await confirmationDialog( context: context, title: 'Bild löschen', - message: 'Bild löschen?'); - if (result != true) return; - // TODO: Uncomment when API is ready - di().showSnackBar( + message: 'Bild löschen?', + ); + if (result != true) return; + // TODO: Uncomment when API is ready + di().showSnackBar( NotificationType.warning, - 'Not implemented yet'); - // await di() - // .deleteAuthorizationFile( - // pupil.internalId, - // authorizationId, - // pupilAuthorization.fileId!, - // ); - }, - child: - // workbook.imageUrl != null - // ? Provider.value( - // updateShouldNotify: (oldValue, newValue) => - // oldValue.documentUrl != - // newValue.documentUrl, - // value: DocumentImageData( - // documentTag: workbook.imageUrl!, - // documentUrl: - // '${di().env!.serverUrl}${WorkbookApiService().getWorkbookImage(workbook.isbn)}', - // size: 100), - // child: const DocumentImage(), - // ) - // : - SizedBox( - height: 100, - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Image.asset('assets/document_camera.png'), - ), + 'Not implemented yet', + ); + // await di() + // .deleteAuthorizationFile( + // pupil.internalId, + // authorizationId, + // pupilAuthorization.fileId!, + // ); + }, + child: + // workbook.imageUrl != null + // ? Provider.value( + // updateShouldNotify: (oldValue, newValue) => + // oldValue.documentUrl != + // newValue.documentUrl, + // value: DocumentImageData( + // documentTag: workbook.imageUrl!, + // documentUrl: + // '${di().env!.serverUrl}${WorkbookApiService().getWorkbookImage(workbook.isbn)}', + // size: 100), + // child: const DocumentImage(), + // ) + // : + SizedBox( + height: 100, + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.asset( + 'assets/document_camera.png', + ), + ), + ), ), - ), - const Gap(10), - ], - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 10, bottom: 8), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text( - workbook.name, - style: const TextStyle( + const Gap(10), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 10, bottom: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + workbook.name, + style: const TextStyle( fontSize: 20, - fontWeight: FontWeight.bold), + fontWeight: FontWeight.bold, + ), + ), ), ), - ), - const Gap(10), - ], - ), - const Gap(5), - // Row( - // children: [ - // const Text('ISBN:'), - // const Gap(10), - // Text( - // workbook.isbn.toString(), - // style: const TextStyle( - // fontSize: 16, - // fontWeight: FontWeight.bold, - // color: Colors.black, - // ), - // ), - // ], - // ), - // const Gap(5), - Row(children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + const Gap(10), + ], + ), + const Gap(5), + // Row( + // children: [ + // const Text('ISBN:'), + // const Gap(10), + // Text( + // workbook.isbn.toString(), + // style: const TextStyle( + // fontSize: 16, + // fontWeight: FontWeight.bold, + // color: Colors.black, + // ), + // ), + // ], + // ), + // const Gap(5), + Row( children: [ - Row( + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - RootCompetenceType - .stringToValue[workbook.subject]! - .value, - overflow: TextOverflow.fade, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - )), + Row( + children: [ + Text( + RootCompetenceType + .stringToValue[workbook.subject]! + .value, + overflow: TextOverflow.fade, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Gap(5), + workbook.level != null + ? GradesWidget( + stringWithGrades: + workbook.level!, + ) + : const Text('nicht vorhanden'), + ], + ), const Gap(5), - workbook.level != null - ? GradesWidget( - stringWithGrades: workbook.level!) - : const Text( - 'nicht vorhanden', + Row( + children: [ + InkWell( + onTap: () async { + if (!(di() + .userName == + pupilWorkbook.createdBy) || + di() + .isAdmin) { + informationDialog( + context, + 'Keine Berechtigung', + 'Arbeitshefte können nur von der eingetragenen Person oder von einem Admin bearbeitet werden!', + ); + return; + } + final createdBy = + await shortTextfieldDialog( + context: context, + title: 'Betreuer:in ändern', + labelText: + 'Betreuer:in eintragen', + hintText: + 'Wer soll das Arbeitsheft betreuen?', + ); + if (createdBy == null) return; + di() + .updatePupilWorkbook( + pupilWorkbook: pupilWorkbook, + createdBy: createdBy, + ); + }, + child: Text( + pupilWorkbook.createdBy, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), + ), + const Gap(2), + const Icon( + Icons.arrow_circle_right_rounded, + color: Colors.orange, + ), + const Gap(2), + Text( + pupilWorkbook.createdAt + .formatDateForUser(), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const Gap(10), ], ), - const Gap(5), - Row( + const Spacer(), + Column( children: [ - InkWell( - onTap: () async { - if (!(di() - .userName == - pupilWorkbook.createdBy) || - di().isAdmin) { - informationDialog( - context, - 'Keine Berechtigung', - 'Arbeitshefte können nur von der eingetragenen Person oder von einem Admin bearbeitet werden!'); - return; - } - final createdBy = - await shortTextfieldDialog( - context: context, - title: 'Betreuer:in ändern', - labelText: - 'Betreuer:in eintragen', - hintText: - 'Wer soll das Arbeitsheft betreuen?'); - if (createdBy == null) return; - di() - .updatePupilWorkbook( - pupilWorkbook: pupilWorkbook, - createdBy: createdBy); - }, - child: Text( - pupilWorkbook.createdBy, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - const Gap(2), - const Icon( - Icons.arrow_circle_right_rounded, - color: Colors.orange, + GrowthDropdown( + dropdownValue: 0, + onChangedFunction: + onChangedGrowthDropdown, ), - const Gap(2), - Text( - pupilWorkbook.createdAt.formatForUser(), - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ) ], ), - const Gap(10), ], ), - const Spacer(), - Column( - children: [ - GrowthDropdown( - dropdownValue: 0, - onChangedFunction: onChangedGrowthDropdown), - ], - ) - ]) - ], + ], + ), ), ), - ), - ], - ), - const Gap(10), - const Text('Kommentar:'), - const Gap(5), - InkWell( - onTap: () async { - final comment = await longTextFieldDialog( + ], + ), + const Gap(10), + const Text('Kommentar:'), + const Gap(5), + InkWell( + onTap: () async { + final comment = await longTextFieldDialog( title: 'Kommentar', initialValue: pupilWorkbook.comment ?? '', labelText: 'Kommentar eintragen', - parentContext: context); - if (comment == null) return; - di().updatePupilWorkbook( - pupilWorkbook: pupilWorkbook, comment: (value: comment)); - }, - child: Text( - pupilWorkbook.comment == null || pupilWorkbook.comment! == '' - ? 'Kein Kommentar' - : pupilWorkbook.comment!, - style: const TextStyle( - fontSize: 16, color: AppColors.interactiveColor), + parentContext: context, + ); + if (comment == null) return; + di().updatePupilWorkbook( + pupilWorkbook: pupilWorkbook, + comment: (value: comment), + ); + }, + child: Text( + pupilWorkbook.comment == null || + pupilWorkbook.comment! == '' + ? 'Kein Kommentar' + : pupilWorkbook.comment!, + style: const TextStyle( + fontSize: 16, + color: AppColors.interactiveColor, + ), + ), ), - ), - ], + ], + ), ), ), - )), + ), ); } } diff --git a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/workbook_card.dart b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/workbook_card.dart index adc4606f..97879f61 100644 --- a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/workbook_card.dart +++ b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/workbook_card.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; -import 'package:school_data_hub_flutter/app_utils/extensions.dart'; +import 'package:school_data_hub_flutter/app_utils/extensions/isbn_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile_content.dart'; @@ -24,106 +24,116 @@ class WorkbookCard extends WatchingWidget { @override Widget build(BuildContext context) { final expansionTileController = createOnce( - () => CustomExpansionTileController()); + () => CustomExpansionTileController(), + ); return ClipRRect( borderRadius: BorderRadius.circular(20), child: Card( - color: Colors.white, - surfaceTintColor: Colors.white, - child: InkWell( - // onTap: () { - // Navigator.of(context).push(MaterialPageRoute( - // builder: (ctx) => SchoolListPupils( - // workbook, - // ), - // )); - // }, - onLongPress: () async { - // if (!di().isAdmin.value) { - // informationDialog(context, 'Keine Berechtigung', - // 'Arbeitshefte können nur von Admins bearbeitet werden!'); - // return; - // } - final bool? result = await confirmationDialog( - context: context, - title: 'Arbeitsheft löschen', - message: - 'Arbeitsheft "${workbook.name}" wirklich löschen? ACHTUNG: Alle Arbeitshefte dieser Art werden ebenfalls gelöscht!'); - if (result == true) { - await di().deleteWorkbook(workbook); - } - }, - child: Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 5), - child: Row( - children: [ - const Gap(15), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Gap(10), - InkWell( - onTap: () async { - final File? file = await createImageFile(context); - if (file == null) return; - // TODO: implement when ready - di().showSnackBar( - NotificationType.warning, 'Not implemented yet'); - // await di() - // .postWorkbookFile(file, workbook.isbn); - }, - onLongPress: (workbook.imageUrl == null) - ? () {} - : () async { - if (workbook.imageUrl == null) { - return; - } - final bool? result = await confirmationDialog( - context: context, - title: 'Bild löschen', - message: 'Bild löschen?'); - if (result != true) return; - // TODO: implement when ready - di().showSnackBar( - NotificationType.warning, - 'Not implemented yet'); + color: Colors.white, + surfaceTintColor: Colors.white, + child: InkWell( + // onTap: () { + // Navigator.of(context).push(MaterialPageRoute( + // builder: (ctx) => SchoolListPupils( + // workbook, + // ), + // )); + // }, + onLongPress: () async { + // if (!di().isAdmin.value) { + // informationDialog(context, 'Keine Berechtigung', + // 'Arbeitshefte können nur von Admins bearbeitet werden!'); + // return; + // } + final bool? result = await confirmationDialog( + context: context, + title: 'Arbeitsheft löschen', + message: + 'Arbeitsheft "${workbook.name}" wirklich löschen? ACHTUNG: Alle Arbeitshefte dieser Art werden ebenfalls gelöscht!', + ); + if (result == true) { + await di().deleteWorkbook(workbook); + } + }, + child: Padding( + padding: const EdgeInsets.only(top: 8.0, bottom: 5), + child: Row( + children: [ + const Gap(15), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Gap(10), + InkWell( + onTap: () async { + final File? file = await createImageFile(context); + if (file == null) return; + // TODO: implement when ready + di().showSnackBar( + NotificationType.warning, + 'Not implemented yet', + ); + // await di() + // .postWorkbookFile(file, workbook.isbn); + }, + onLongPress: (workbook.imageUrl == null) + ? () {} + : () async { + if (workbook.imageUrl == null) { + return; + } + final bool? result = await confirmationDialog( + context: context, + title: 'Bild löschen', + message: 'Bild löschen?', + ); + if (result != true) return; + // TODO: implement when ready + di().showSnackBar( + NotificationType.warning, + 'Not implemented yet', + ); - // await di() - // .deleteWorkbookFile(workbook.isbn); - }, - child: workbook.imageUrl != null - ? UnencryptedImageInCard( - cacheKey: workbook.isbn.toString(), - path: workbook.imageUrl, - size: 75, - ) - : SizedBox( - height: 100, - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: - Image.asset('assets/document_camera.png'), + // await di() + // .deleteWorkbookFile(workbook.isbn); + }, + child: workbook.imageUrl != null + ? UnencryptedImageInCard( + cacheKey: workbook.isbn.toString(), + path: workbook.imageUrl, + size: 75, + ) + : SizedBox( + height: 100, + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.asset( + 'assets/document_camera.png', ), ), - ), - const Gap(10), - ], - ), - Expanded( - child: Padding( - padding: - const EdgeInsets.only(top: 8.0, left: 15, bottom: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: InkWell( - onLongPress: (di().isAdmin) - ? () async { - Navigator.of(context) - .push(MaterialPageRoute( + ), + ), + const Gap(10), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 15, + bottom: 8, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: InkWell( + onLongPress: (di().isAdmin) + ? () async { + Navigator.of(context).push( + MaterialPageRoute( builder: (ctx) => NewWorkbookPage( workbook: workbook, name: workbook.name, @@ -132,110 +142,117 @@ class WorkbookCard extends WatchingWidget { level: workbook.level, isEdit: true, ), - )); - } - : () {}, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text( - workbook.name, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold), + ), + ); + } + : () {}, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + workbook.name, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, ), ), ), ), - const Gap(10), - ], - ), - const Gap(5), - Row( - children: [ - const Text('ISBN:'), - const Gap(10), - SelectableText( - workbook.isbn.displayAsIsbn(), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + ), + const Gap(10), + ], + ), + const Gap(5), + Row( + children: [ + const Text('ISBN:'), + const Gap(10), + SelectableText( + workbook.isbn.displayAsIsbn(), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, ), - ], - ), - const Gap(5), - Row( - children: [ - const Text('Kompetenzbereich(e):'), - const Gap(10), - Text( - workbook.subject ?? 'nicht angegeben', - overflow: TextOverflow.fade, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + ), + ], + ), + const Gap(5), + Row( + children: [ + const Text('Kompetenzbereich(e):'), + const Gap(10), + Text( + workbook.subject ?? 'nicht angegeben', + overflow: TextOverflow.fade, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, ), - ], - ), - const Gap(5), - Row( - children: [ - const Text('Kompetenzstufe:'), - const Gap(10), - workbook.level != null - ? GradesWidget( - stringWithGrades: workbook.level!) - : const Text( - 'nicht angegeben', - overflow: TextOverflow.fade, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + ), + ], + ), + const Gap(5), + Row( + children: [ + const Text('Kompetenzstufe:'), + const Gap(10), + workbook.level != null + ? GradesWidget( + stringWithGrades: workbook.level!, + ) + : const Text( + 'nicht angegeben', + overflow: TextOverflow.fade, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, ), - ], - ), - const Gap(5), - Row( - children: [ - const Text('Bestand:'), - const Gap(10), - Text( - workbook.amount.toString(), - overflow: TextOverflow.fade, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + ), + ], + ), + const Gap(5), + Row( + children: [ + const Text('Bestand:'), + const Gap(10), + Text( + workbook.amount.toString(), + overflow: TextOverflow.fade, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const Spacer(), + CustomExpansionTileSwitch( + expansionSwitchWidget: const Icon( + Icons.arrow_downward, ), - const Spacer(), - CustomExpansionTileSwitch( - expansionSwitchWidget: - const Icon(Icons.arrow_downward), - customExpansionTileController: - expansionTileController), - const Gap(5) - ], - ), - const Gap(10), - CustomExpansionTileContent( - tileController: expansionTileController, - widgetList: [ - // TODO: Add cards with pupilworkbooks - ]), - ], - ), + customExpansionTileController: + expansionTileController, + ), + const Gap(5), + ], + ), + const Gap(10), + CustomExpansionTileContent( + tileController: expansionTileController, + widgetList: [ + // TODO: Add cards with pupilworkbooks + ], + ), + ], ), ), - ], - ), + ), + ], ), - )), + ), + ), + ), ); } } diff --git a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 65c2ebd4..8e1389eb 100644 Binary files a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 474290eb..5689fb65 100644 Binary files a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 6acfcd68..e90f50e6 100644 Binary files a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index 0b2eb987..f085db8b 100644 Binary files a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 6aa31251..f9edc627 100644 Binary files a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index 8ed5fead..13470685 100644 Binary files a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 74e8d185..8321ac82 100644 Binary files a/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/school_data_hub_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/school_data_hub_flutter/pubspec.lock b/school_data_hub_flutter/pubspec.lock index 9a6358f9..d390d6a0 100644 --- a/school_data_hub_flutter/pubspec.lock +++ b/school_data_hub_flutter/pubspec.lock @@ -249,6 +249,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + community_charts_common: + dependency: transitive + description: + name: community_charts_common + sha256: d997ade57f15490346de46efbe23805d378a672aafbf5e47e19517964b671009 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + community_charts_flutter: + dependency: "direct main" + description: + name: community_charts_flutter + sha256: "4614846b99782ab79b613687704865e5468ecada3f0ad1afe1cdc3ff5b727f72" + url: "https://pub.dev" + source: hosted + version: "1.0.4" connectivity_plus: dependency: transitive description: diff --git a/school_data_hub_flutter/pubspec.yaml b/school_data_hub_flutter/pubspec.yaml index 0b50b02e..5bfc6810 100644 --- a/school_data_hub_flutter/pubspec.yaml +++ b/school_data_hub_flutter/pubspec.yaml @@ -77,6 +77,7 @@ dependencies: flex_color_picker: ^3.7.1 msix: ^3.16.12 uml_for_serverpod: ^0.0.5 + community_charts_flutter: ^1.0.4 dev_dependencies: diff --git a/school_data_hub_flutter/web/favicon.png b/school_data_hub_flutter/web/favicon.png index 6acfcd68..e90f50e6 100644 Binary files a/school_data_hub_flutter/web/favicon.png and b/school_data_hub_flutter/web/favicon.png differ diff --git a/school_data_hub_flutter/web/icons/Icon-192.png b/school_data_hub_flutter/web/icons/Icon-192.png index 87bf36f7..262eb9c6 100644 Binary files a/school_data_hub_flutter/web/icons/Icon-192.png and b/school_data_hub_flutter/web/icons/Icon-192.png differ diff --git a/school_data_hub_flutter/web/icons/Icon-512.png b/school_data_hub_flutter/web/icons/Icon-512.png index 8ed5fead..13470685 100644 Binary files a/school_data_hub_flutter/web/icons/Icon-512.png and b/school_data_hub_flutter/web/icons/Icon-512.png differ diff --git a/school_data_hub_flutter/web/icons/Icon-maskable-192.png b/school_data_hub_flutter/web/icons/Icon-maskable-192.png index 87bf36f7..262eb9c6 100644 Binary files a/school_data_hub_flutter/web/icons/Icon-maskable-192.png and b/school_data_hub_flutter/web/icons/Icon-maskable-192.png differ diff --git a/school_data_hub_flutter/web/icons/Icon-maskable-512.png b/school_data_hub_flutter/web/icons/Icon-maskable-512.png index 8ed5fead..13470685 100644 Binary files a/school_data_hub_flutter/web/icons/Icon-maskable-512.png and b/school_data_hub_flutter/web/icons/Icon-maskable-512.png differ diff --git a/school_data_hub_flutter/windows/runner/resources/app_icon.ico b/school_data_hub_flutter/windows/runner/resources/app_icon.ico index fc6e2dd8..52209b01 100644 Binary files a/school_data_hub_flutter/windows/runner/resources/app_icon.ico and b/school_data_hub_flutter/windows/runner/resources/app_icon.ico differ diff --git a/school_data_hub_server/.gitignore b/school_data_hub_server/.gitignore index 1edd2d25..e741d658 100644 --- a/school_data_hub_server/.gitignore +++ b/school_data_hub_server/.gitignore @@ -17,6 +17,9 @@ doc/api/ # Passwords file config/passwords.yaml +# Docker +docker-compose.yaml + # Firebase service account key for Firebase auth config/firebase_service_account_key.json diff --git a/school_data_hub_server/Makefile b/school_data_hub_server/Makefile index 7d5301ad..cfe13908 100644 --- a/school_data_hub_server/Makefile +++ b/school_data_hub_server/Makefile @@ -1,4 +1,4 @@ -generate: ## generate updated models and create migrations +migration: ## generate updated models and create migrations serverpod generate serverpod create-migration dart run bin/main.dart --role maintenance --apply-migrations @@ -8,6 +8,7 @@ docker: ## run docker container reset: ## reset the server + @powershell -NoProfile -Command "$$bold = [char]27 + '[1m'; $$reset = [char]27 + '[0m'; Write-Host \"$${bold}WARNING: This will reset the server and delete all data. Continue? (y/n)$${reset}\" -ForegroundColor Red -NoNewline; $$r = Read-Host; if ($$r -ne 'y' -and $$r -ne 'Y') { Write-Host 'Cancelled.' -ForegroundColor Yellow; exit 1 } else { Write-Host 'Resetting server...' -ForegroundColor Green }" cmd /c rmdir /Q /S migrations cmd /c mkdir migrations cmd /c rmdir /Q /S storage\private @@ -51,4 +52,8 @@ uml: ## generate UML diagram dart run uml_for_serverpod --config=uml_config.yaml install: ## serverpod vps branch - dart pub global activate -sgit https://github.com/dart-lang/http.git --git-ref dart pub global activate -sgit https://github.com/dabblingwithcode/serverpod_vps.git --git-ref 36f98e900347335af2338a0e087538009b7de2f9 \ No newline at end of file + dart pub global activate -sgit https://github.com/dart-lang/http.git --git-ref dart pub global activate -sgit https://github.com/dabblingwithcode/serverpod_vps.git --git-ref 36f98e900347335af2338a0e087538009b7de2f9 + +confirm: ## yes/no prompt (Windows compatible) + @powershell -NoProfile -Command "$$bold = [char]27 + '[1m'; $$reset = [char]27 + '[0m'; Write-Host \"$${bold}WARNING: This will create a migration. Continue? (y/n)$${reset}\" -ForegroundColor Red -NoNewline; $$r = Read-Host; if ($$r -ne 'y' -and $$r -ne 'Y') { Write-Host 'Cancelled.' -ForegroundColor Yellow; exit 1 } else { Write-Host 'Proceeding...' -ForegroundColor Green }" + serverpod create-migration diff --git a/school_data_hub_server/README.md b/school_data_hub_server/README.md index 825f76bd..d5e2c518 100644 --- a/school_data_hub_server/README.md +++ b/school_data_hub_server/README.md @@ -1,7 +1,482 @@ -# school_data_hub_server +# School Data Hub Server +The Serverpod backend server for School Data Hub. This server provides the API endpoints, database management, and business logic for the School Data Hub application. +## Overview -#### Reach docker container from the cli -list containers: docker ps -docker exec -it ash \ No newline at end of file +The server is built with Serverpod 2.9.1, a Dart-based server framework that provides: +- RESTful API endpoints +- PostgreSQL database integration +- Redis for caching and sessions +- Authentication and authorization +- File storage management +- Background tasks (future calls) + +## Prerequisites + +- **Dart SDK**: >=3.3.0 +- **Docker & Docker Compose**: For running PostgreSQL and Redis +- **Serverpod CLI**: Install globally with: + ```bash + dart pub global activate serverpod_cli + ``` + +## Quick Start + +### Using Docker Compose (Recommended) + +The easiest way to get started is using Docker Compose: + +1. Start the required services (PostgreSQL and Redis): + ```bash + docker compose up -d + ``` + + This starts: + - PostgreSQL on port 8090 (development) and 9090 (test) + - Redis on port 8091 (development) and 9091 (test) + +2. Generate server code and create migrations: + ```bash + make generate + ``` + + Or manually: + ```bash + serverpod generate + serverpod create-migration + dart run bin/main.dart --role maintenance --apply-migrations + ``` + +3. Run the server: + ```bash + make run + ``` + + Or manually: + ```bash + dart run bin/main.dart --apply-migrations + ``` + +The server will start on `http://localhost:8080` by default. + +### Configuration + +Configuration files are located in the `config/` directory: +- `development.yaml` - Development environment +- `staging.yaml` - Staging environment +- `production.yaml` - Production environment +- `test.yaml` - Test environment + +Edit these files to configure: +- Database connection strings +- Redis connection settings +- Server ports and URLs +- Email server configuration +- Storage paths + +## Project Structure + +``` +lib/ +├── server.dart # Server entry point +└── src/ + ├── _features/ # Feature modules + │ ├── admin/ # Admin endpoints + │ ├── attendance/ # Attendance management + │ ├── auth/ # Authentication + │ ├── authorizations/ # Authorization management + │ ├── books/ # Library management + │ ├── learning/ # Competence tracking + │ ├── learning_support/ # Learning support plans + │ ├── matrix/ # Matrix integration + │ ├── pupil/ # Pupil data management + │ ├── school_data/ # School configuration + │ ├── school_lists/ # School lists + │ ├── schoolday/ # Schoolday management + │ ├── schoolday_events/ # Schoolday events + │ ├── timetable/ # Timetable management + │ ├── user/ # User management + │ └── workbooks/ # Workbook management + ├── _shared/ # Shared models and endpoints + ├── future_calls/ # Background tasks + ├── generated/ # Auto-generated code + ├── helpers/ # Helper functions + ├── schemas/ # Database schemas + └── utils/ # Utilities (mailer, logger, etc.) +config/ # Environment configurations +migrations/ # Database migration files +storage/ # File storage +├── private/ # Private files (avatars, documents, etc.) +└── public/ # Public files +deploy/ # Deployment configurations +├── aws/ # AWS deployment (Terraform) +└── gcp/ # GCP deployment +``` + +## Development + +### Running the Server + +Start the server in development mode: +```bash +make run +``` + +Or: +```bash +dart run bin/main.dart --apply-migrations +``` + +The `--apply-migrations` flag automatically applies any pending database migrations. + +### Generating Code + +When you modify models or endpoints, regenerate the code: +```bash +make generate +``` + +This runs: +1. `serverpod generate` - Generates protocol and client code +2. `serverpod create-migration` - Creates database migration +3. Applies the migration to the database + +### Database Migrations + +#### Creating Migrations + +After modifying models, create a migration: +```bash +serverpod create-migration +``` + +#### Applying Migrations + +Migrations are automatically applied when running with `--apply-migrations`, or manually: +```bash +dart run bin/main.dart --role maintenance --apply-migrations +``` + +#### Reviewing Migrations + +Migrations are stored in `migrations/` directory. Each migration includes: +- `definition.json` - Model definitions +- `definition.sql` - SQL schema changes +- `migration.sql` - Migration SQL script + +### Testing + +Run tests: +```bash +dart test +``` + +For integration tests, ensure test services are running: +```bash +docker compose up -d +``` + +### Resetting the Development Environment + +**Windows:** +```bash +make reset +``` + +**macOS/Linux:** +```bash +make reset_mac +``` + +This will: +- Remove all migrations and storage data +- Recreate directory structure +- Regenerate code and create new migrations +- Reset Docker volumes +- Apply migrations + +⚠️ **Warning**: This deletes all data! Only use in development. + +## Docker Management + +### Accessing Database Containers + +List running containers: +```bash +docker ps +``` + +Access PostgreSQL container shell: +```bash +docker exec -it ash +``` + +Or using the service name: +```bash +docker exec -it school_data_hub_server-postgres-1 ash +``` + +### Database Access + +Connect to PostgreSQL: +```bash +# Development database +docker exec -it school_data_hub_server-postgres-1 psql -U postgres -d school_data_hub + +# Test database +docker exec -it school_data_hub_server-postgres_test-1 psql -U postgres -d school_data_hub_test +``` + +### Stopping Services + +Stop all services: +```bash +docker compose down +``` + +Stop and remove volumes (⚠️ deletes data): +```bash +docker compose down --volumes +``` + +## Deployment + +The server supports automated deployment via GitHub Actions. Multiple deployment strategies are available: + +### GitHub Actions Deployment + +GitHub Actions workflows are configured in `.github/workflows/`: + +#### Docker Deployment (VPS) + +Automated Docker deployment to Virtual Private Servers (VPS): + +**Workflows:** +- `deployment-docker.yml` - Production deployment (triggers on `main` branch) +- `deployment-docker-staging.yml` - Staging deployment (triggers on `develop` or `staging` branches) + +**How it works:** + +1. **Build and Push**: Builds Docker image and pushes to GitHub Container Registry (ghcr.io) +2. **Deploy**: Connects to VPS via SSH and deploys using Docker Compose + +**Prerequisites:** + +Set up the following GitHub Secrets in your repository: + +**SSH Configuration:** +- `SSH_PRIVATE_KEY` - Private SSH key for VPS access +- `SSH_USER` - SSH username (e.g., "github-actions") +- `SSH_HOST` - Production VPS hostname/IP +- `SSH_STAGING_HOST` - Staging VPS hostname/IP (for staging workflow) + +**GitHub Access:** +- `PAT_GITHUB` or `PAT_TOKEN` - GitHub Personal Access Token +- `PAT_USER_GITHUB` - GitHub username for the PAT + +**Production Server Configuration:** +- `SERVERPOD_DATABASE_NAME` - Database name +- `SERVERPOD_DATABASE_USER` - Database username +- `SERVERPOD_DATABASE_PASSWORD` - Database password +- `SERVERPOD_API_SERVER_PUBLIC_HOST` - API server domain +- `SERVERPOD_INSIGHTS_SERVER_PUBLIC_HOST` - Insights server domain +- `SERVERPOD_WEB_SERVER_PUBLIC_HOST` - Web server domain +- `SERVERPOD_MAX_REQUEST_SIZE` - Maximum request size in bytes +- `SERVERPOD_SERVICE_SECRET` - Service secret (minimum 20 characters) + +**Staging Server Configuration:** +- `SERVERPOD_STAGING_DATABASE_NAME` - Staging database name +- `SERVERPOD_STAGING_DATABASE_USER` - Staging database username +- `SERVERPOD_STAGING_DATABASE_PASSWORD` - Staging database password +- `SERVERPOD_STAGING_API_SERVER_PUBLIC_HOST` - Staging API server domain +- `SERVERPOD_STAGING_INSIGHTS_SERVER_PUBLIC_HOST` - Staging Insights server domain +- `SERVERPOD_STAGING_WEB_SERVER_PUBLIC_HOST` - Staging Web server domain +- `SERVERPOD_STAGING_MAX_REQUEST_SIZE` - Staging max request size +- `SERVERPOD_STAGING_SERVICE_SECRET` - Staging service secret + +**Optional Mail Configuration (Staging):** +- `SERVERPOD_MAIL_USERNAME` - SMTP username +- `SERVERPOD_MAIL_PASSWORD` - SMTP password +- `SERVERPOD_MAIL_SMTP_HOST` - SMTP host +- `SERVERPOD_MAIL_ADMIN` - Admin email address + +**Deployment Process:** + +1. **Automatic Trigger**: Pushes to `main` branch trigger production deployment +2. **Manual Trigger**: Go to **Actions** tab → Select workflow → **Run workflow** + +The workflow will: +- Build Docker image for ARM64/ARMv7 architectures +- Push image to GitHub Container Registry +- Connect to VPS via SSH +- Pull latest image and restart services using Docker Compose + +**Configuration:** + +Update `.github/workflows/deployment-docker.yml`: +- `GHCR_ORG` - Your GitHub organization/username +- `branches` - Branches that trigger deployment + +See `.github/workflows/deployment-docker.md` for detailed VPS setup instructions. + +#### AWS Deployment + +Deploy to AWS using CodeDeploy: + +**Workflow:** `deployment-aws.yml` + +**Trigger:** +- Pushes to `deployment-aws-production` or `deployment-aws-staging` branches +- Manual dispatch with target selection + +**Prerequisites:** + +Set up AWS secrets: +- `AWS_ACCESS_KEY_ID` - AWS access key +- `AWS_SECRET_ACCESS_KEY` - AWS secret key +- `SERVERPOD_PASSWORDS` - Serverpod passwords configuration (YAML content) + +**Configuration:** + +Update workflow variables: +- `DEPLOYMENT_BUCKET` - S3 bucket for deployments +- `AWS_NAME` - Application name in AWS + +The workflow: +1. Compiles the server +2. Creates deployment package +3. Uploads to S3 +4. Triggers AWS CodeDeploy deployment + +**Infrastructure:** + +Terraform configurations are available in `deploy/aws/terraform/` for: +- EC2 instances +- RDS database +- Redis cache +- CloudFront distribution + +#### GCP Deployment + +Deploy to Google Cloud Platform: + +**Workflow:** `deployment-gcp.yml` + +**Trigger:** +- Pushes to `deployment-gcp-production` or `deployment-gcp-staging` branches +- Manual dispatch with target selection + +**Prerequisites:** + +Set up GCP secrets: +- `GOOGLE_CREDENTIALS` - GCP service account credentials (JSON) +- `SERVERPOD_PASSWORDS` - Serverpod passwords configuration + +**Configuration:** + +Update workflow environment variables: +- `PROJECT` - GCP project ID +- `REGION` - GCP region (default: us-central1) +- `ZONE` - GCP zone (default: us-central1-c) + +The workflow: +1. Authenticates to Google Cloud +2. Builds Docker image +3. Pushes to Google Container Registry +4. (Optional) Restarts instances in managed instance group + +**Infrastructure:** + +Terraform and Cloud Run configurations are available in `deploy/gcp/`. + +### Manual Docker Deployment + +For manual deployment without GitHub Actions: + +Production Docker configuration is available in: +- `docker-compose.production.yaml` +- `docker-compose.staging.yaml` +- `Dockerfile.prod` + +### Cloud Deployment + +Deployment configurations are available in `deploy/`: +- **AWS**: Terraform configurations for EC2, RDS, Redis, and CloudFront +- **GCP**: Configurations for Cloud Run and GCE + +See `deploy/` directories for platform-specific deployment instructions. + +## Features + +### Background Tasks (Future Calls) + +The server supports background tasks: +- `database_backup_future_call.dart` - Automated database backups +- `increase_credit_future_call.dart` - Credit management tasks + +### Mail Service + +Email functionality is implemented via the `mailer` package. Configuration is managed through environment config files. + +See `lib/src/utils/MAILER_README.md` for mail service documentation. + +### Storage + +File storage is organized in: +- `storage/private/` - Encrypted, private files (avatars, documents, events) +- `storage/public/` - Publicly accessible files + +### UML Diagram Generation + +Generate UML diagrams of your models: +```bash +make uml +``` + +This uses `uml_for_serverpod` to generate diagrams based on `uml_config.yaml`. + +## Database Backups + +Backups are stored in `database_backups/`. Automated backups can be configured through future calls. + +## Logging + +The server uses the `logging` package. Configure log levels and outputs in your environment configuration files. + +## Security + +- All sensitive data is encrypted +- Authentication via Serverpod Auth +- Authorization through user scopes +- Secure file storage for private documents + +## Troubleshooting + +### Common Issues + +1. **Port Already in Use**: Change the port in your configuration file or stop the conflicting service. + +2. **Database Connection Failed**: Ensure Docker containers are running: + ```bash + docker compose ps + ``` + +3. **Migration Errors**: Review migration files in `migrations/` and ensure they're in the correct order. + +4. **Storage Permission Errors**: Ensure the `storage/` directory has proper write permissions. + +## Makefile Commands + +- `make generate` - Generate code and apply migrations +- `make docker` - Start Docker containers +- `make reset` - Reset development environment (Windows) +- `make reset_mac` - Reset development environment (macOS/Linux) +- `make run` - Run the server +- `make uml` - Generate UML diagrams + +## Resources + +- [Serverpod Documentation](https://serverpod.dev) +- [Main README](../README.md) +- [Serverpod GitHub](https://github.com/serverpod/serverpod) diff --git a/school_data_hub_server/docker-compose.production.yaml b/school_data_hub_server/docker-compose.production.yaml index cbc8c55a..b26c725d 100644 --- a/school_data_hub_server/docker-compose.production.yaml +++ b/school_data_hub_server/docker-compose.production.yaml @@ -73,6 +73,13 @@ services: - SERVERPOD_WEB_SERVER_PORT - SERVERPOD_SERVICE_SECRET - SERVERPOD_MAX_REQUEST_SIZE + - SERVERPOD_MAIL_USERNAME + - SERVERPOD_MAIL_PASSWORD + - SERVERPOD_MAIL_SMTP_HOST + - SERVERPOD_MAIL_ADMIN + - MATRIX_SERVER_URL + - MATRIX_AUTH_TOKEN + command: [ "--mode", diff --git a/school_data_hub_server/docker-compose.staging.yaml b/school_data_hub_server/docker-compose.staging.yaml index c3c622d8..6014b419 100644 --- a/school_data_hub_server/docker-compose.staging.yaml +++ b/school_data_hub_server/docker-compose.staging.yaml @@ -72,6 +72,12 @@ services: - SERVERPOD_WEB_SERVER_PORT - SERVERPOD_SERVICE_SECRET - SERVERPOD_MAX_REQUEST_SIZE + - SERVERPOD_MAIL_USERNAME + - SERVERPOD_MAIL_PASSWORD + - SERVERPOD_MAIL_SMTP_HOST + - SERVERPOD_MAIL_ADMIN + - MATRIX_SERVER_URL + - MATRIX_AUTH_TOKEN command: [ "--mode", diff --git a/school_data_hub_server/lib/server.dart b/school_data_hub_server/lib/server.dart index 312d2c9d..d9846191 100644 --- a/school_data_hub_server/lib/server.dart +++ b/school_data_hub_server/lib/server.dart @@ -5,16 +5,17 @@ import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:school_data_hub_server/src/future_calls/database_backup_future_call.dart'; import 'package:school_data_hub_server/src/future_calls/increase_credit_future_call.dart'; +import 'package:school_data_hub_server/src/generated/protocol.dart'; import 'package:school_data_hub_server/src/helpers/create_local_storage_directories.dart'; import 'package:school_data_hub_server/src/helpers/populate_test_environment.dart'; import 'package:school_data_hub_server/src/utils/local_storage.dart'; import 'package:school_data_hub_server/src/utils/logger/logrecord_formatter.dart'; +import 'package:school_data_hub_server/src/utils/mailer.dart'; import 'package:school_data_hub_server/src/web/routes/root.dart'; import 'package:serverpod/serverpod.dart'; import 'package:serverpod_auth_server/serverpod_auth_server.dart' as auth; import 'src/generated/endpoints.dart'; -import 'src/generated/protocol.dart'; // This is the starting point of your Serverpod server. In most cases, you will // only need to make additions to this file if you add future calls, are @@ -56,7 +57,14 @@ void run(List args) async { Endpoints(), authenticationHandler: auth.authenticationHandler, ); - + pod.loadCustomPasswords([ + (envName: 'MATRIX_SERVER_URL', alias: 'matrixServerUrl'), + (envName: 'MATRIX_AUTH_TOKEN', alias: 'matrixAuthToken'), + (envName: 'SERVERPOD_MAIL_USERNAME', alias: 'emailUsername'), + (envName: 'SERVERPOD_MAIL_PASSWORD', alias: 'emailPassword'), + (envName: 'SERVERPOD_MAIL_SMTP_HOST', alias: 'emailSmtpHost'), + (envName: 'SERVERPOD_MAIL_ADMIN', alias: 'emailAdmin'), + ]); // Configure storage with environment-aware paths String storageBasePath; @@ -101,8 +109,51 @@ void run(List args) async { var session = await pod.createSession(); + // Initialize MailerService from environment variables if available + final mailUsername = Serverpod.instance.getPassword('emailUsername'); + final mailPassword = Serverpod.instance.getPassword('emailPassword'); + final mailSmtpHost = Serverpod.instance.getPassword('emailSmtpHost'); + + bool mailerInitialized = false; + + if (mailUsername != null && + mailPassword != null && + mailSmtpHost != null && + mailUsername.isNotEmpty && + mailPassword.isNotEmpty && + mailSmtpHost.isNotEmpty) { + try { + MailerService.instance.initialize( + username: mailUsername, + password: mailPassword, + smtpHost: mailSmtpHost, + smtpPort: 587, // Hardcoded as per requirements + fromName: 'Schuldaten Benachrichtigungen', + defaultRecipient: '', + ); + mailerInitialized = true; + _logger.info( + 'MailerService initialized successfully from environment variables'); + } catch (e) { + _logger.severe('Failed to initialize MailerService: $e'); + } + } + + // Try to initialize from session passwords as fallback + if (!mailerInitialized) { + final initialized = MailerService.instance.initializeFromSession(session); + if (initialized) { + mailerInitialized = true; + _logger.info( + 'MailerService initialized successfully from session passwords'); + } else { + _logger.warning( + 'Mail configuration not found in session passwords. Email functionality will not be available.'); + } + } + // Check if there are any users in the database. If not, we need to populate the test environment. - final userCount = await auth.UserInfo.db.count(session); + final userCount = await session.db.count(); _logger.info('Current user count in database: $userCount'); final adminUser = await auth.UserInfo.db.findFirstRow( @@ -132,22 +183,28 @@ void run(List args) async { ); // Send startup notification email - // try { - // MailerService.instance.initializeFromSession(session); - // final success = await MailerService.instance.sendNotification( - // recipient: '', - // subject: 'Server Started', - // message: 'School Data Hub Server has started successfully.\n\n' - // 'Timestamp: ${DateTime.now().toIso8601String()}\n' - // 'User count: $userCount', - // ); - - // if (success) { - // _logger.info('Startup notification email sent successfully'); - // } else { - // _logger.severe('Failed to send startup notification email'); - // } - // } catch (e) { - // _logger.severe('Error sending startup notification email: $e'); - // } + try { + final emailAdmin = Serverpod.instance.getPassword('emailAdmin'); + if (emailAdmin != null && emailAdmin.isNotEmpty) { + final success = await MailerService.instance.sendNotification( + recipient: emailAdmin, + subject: 'Server Started', + message: 'School Data Hub Server has started successfully.\n\n' + 'Timestamp: ${DateTime.now().toIso8601String()}\n' + 'User count: $userCount', + ); + + if (success) { + _logger.info('Startup notification email sent successfully'); + } else { + _logger.warning( + 'Failed to send startup notification email (service may not be initialized)'); + } + } else { + _logger.info( + 'Email admin address not configured, skipping startup notification'); + } + } catch (e) { + _logger.severe('Error sending startup notification email: $e'); + } } diff --git a/school_data_hub_server/lib/src/_features/admin/endpoints/admin_endpoint.dart b/school_data_hub_server/lib/src/_features/admin/endpoints/admin_endpoint.dart index 8af19fda..e1f7865e 100644 --- a/school_data_hub_server/lib/src/_features/admin/endpoints/admin_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/admin/endpoints/admin_endpoint.dart @@ -30,6 +30,9 @@ class AdminEndpoint extends Endpoint { required int reliefTimeUnits, required List scopeNames, required bool isTester, + String? schooldayEventsProcessingTeam, + String? matrixUserId, + int? credit, }) async { session.log('Creating user: $userName, $email'); final UserInfo? userInfo = @@ -45,17 +48,17 @@ class AdminEndpoint extends Endpoint { await auth.UserInfo.db.updateRow(session, userInfo); // Convert string scopes to Scope objects - // TODO: fix this when we use more scopes - bool isAdmin = false; + Set scopes = {}; for (final scope in scopeNames) { - if (scope.contains('admin')) { - isAdmin = true; + if (scope == 'admin') { + scopes.add(Scope('serverpod.admin')); + } else { + scopes.add(Scope(scope)); } } // Update scopes if provided - await auth.Users.updateUserScopes( - session, userInfo.id!, isAdmin ? {Scope('serverpod.admin')} : {}); + await auth.Users.updateUserScopes(session, userInfo.id!, scopes); // Create a new User object and insert it into the database final newUser = User( userInfoId: userInfo.id!, @@ -70,7 +73,8 @@ class AdminEndpoint extends Endpoint { role: role, timeUnits: timeUnits, reliefTimeUnits: reliefTimeUnits, - credit: 50, + credit: credit ?? 50, + matrixUserId: matrixUserId, ); await User.db.insertRow(session, newUser); diff --git a/school_data_hub_server/lib/src/_features/pupil/endpooints/pupil_update_endpoint.dart b/school_data_hub_server/lib/src/_features/pupil/endpooints/pupil_update_endpoint.dart index 163fe796..6afcebd2 100644 --- a/school_data_hub_server/lib/src/_features/pupil/endpooints/pupil_update_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/pupil/endpooints/pupil_update_endpoint.dart @@ -391,4 +391,22 @@ class PupilUpdateEndpoint extends Endpoint { ); return updatedPupilWithRelation!; } + + Future updateAfterSchoolCare( + Session session, int pupilId, AfterSchoolCare afterSchoolCare) async { + final pupil = await PupilData.db + .findById(session, pupilId, include: PupilSchemas.allInclude); + if (pupil == null) { + throw Exception('Pupil not found'); + } + pupil.afterSchoolCare = afterSchoolCare; + await PupilData.db.updateRow(session, pupil); + // Fetch the object again with the relation included + final updatedPupilWithRelation = await PupilData.db.findById( + session, + pupil.id!, + include: PupilSchemas.allInclude, + ); + return updatedPupilWithRelation!; + } } diff --git a/school_data_hub_server/lib/src/_features/schoolday_events/endpoints/schoolday_event_endpoint.dart b/school_data_hub_server/lib/src/_features/schoolday_events/endpoints/schoolday_event_endpoint.dart index 3ca43ece..8f4fbb49 100644 --- a/school_data_hub_server/lib/src/_features/schoolday_events/endpoints/schoolday_event_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/schoolday_events/endpoints/schoolday_event_endpoint.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:logging/logging.dart'; import 'package:school_data_hub_server/src/generated/protocol.dart'; -import 'package:school_data_hub_server/src/utils/mailer.dart'; +import 'package:school_data_hub_server/src/utils/matrix_notifications/matrix_notifications.dart'; +import 'package:school_data_hub_server/src/utils/matrix_notifications/schoolday_event_notification_text.dart'; import 'package:serverpod/serverpod.dart'; final _log = Logger('SchooldayEventEndpoint'); @@ -20,10 +23,13 @@ class SchooldayEventEndpoint extends Endpoint { Future createSchooldayEvent(Session session, {required int pupilId, + required String pupilNameAndGroup, + required String dateTimeAsString, required int schooldayId, required SchooldayEventType type, required String reason, - required String createdBy}) async { + required String createdBy, + required String tutor}) async { final eventId = Uuid().v4(); final schooldayEvent = SchooldayEvent( @@ -46,35 +52,94 @@ class SchooldayEventEndpoint extends Endpoint { processedDocument: HubDocument.include(), ), ); - // TODO: Need to implement the mail secrets in the github actions secrets + try { final pupil = await PupilData.db.findFirstRow(session, - where: (t) => t.id.equals(eventWithSchoolday!.pupilId)); - - MailerService.instance.initializeFromSession(session); - final success = await MailerService.instance.sendNotification( - recipient: session.passwords['schoolEmail'] ?? '', - subject: 'Neues Schulereignis', - message: 'Es wurde ein neues Schulereignis erstellt.\n\n' - 'Es ist das Schulereignis Nummer ${pupil?.schooldayEvents?.length}', + where: (t) => t.id.equals(eventWithSchoolday!.pupilId), + include: + PupilData.include(schooldayEvents: SchooldayEvent.includeList())); + final numberOfEventsOfTheSameType = pupil?.schooldayEvents + ?.where((event) => event.eventType == type) + .length ?? + 0; + final recipients = + await MatrixNotifications.instance.findNotificationRecipients( + session: session, + pupilNameAndGroup: pupilNameAndGroup, + tutor: tutor, ); - if (success) { - _log.info('Startup notification email sent successfully'); - } else { - _log.severe('Failed to send startup notification email'); + // Send notification to all recipients + if (recipients.isEmpty) { + // Fallback to default recipient if no matches found + _log.warning('No recipients found for schoolday event $eventId'); + return eventWithSchoolday!; } + + unawaited(MatrixNotifications.instance.sendDirectTextMessage( + session: session, + recipients: recipients.toList(), + text: getSchooldayEventNotificationText( + eventcreator: createdBy, + pupilName: pupilNameAndGroup, + dateTimeAsString: dateTimeAsString, + schooldayEvent: eventWithSchoolday!, + numberOfEvents: numberOfEventsOfTheSameType), + html: getSchooldayEventNotificationHtml( + eventcreator: createdBy, + pupilName: pupilNameAndGroup, + dateTimeAsString: dateTimeAsString, + schooldayEvent: eventWithSchoolday, + numberOfEvents: numberOfEventsOfTheSameType), + )); + // final success = await MailerService.instance.sendNotification( + // recipient: recipient?.userInfo?.email ?? '', + // subject: 'Neues Schulereignis', + // message: 'Es wurde ein neues Schulereignis erstellt.\n\n' + // 'Es ist das Schulereignis Nummer ${pupil?.schooldayEvents?.length}'); } catch (e) { - _log.severe('Error sending startup notification email: $e'); + _log.severe('Error sending matrix notification: $e'); } return eventWithSchoolday!; } - Future updateSchooldayEvent(Session session, - SchooldayEvent schooldayEvent, bool changedProcessedToFalse) async { + Future updateSchooldayEvent( + Session session, + SchooldayEvent schooldayEvent, + bool changedProcessedStatus, + String pupilNameAndGroup, + String tutor, + String modifiedBy, + String dateTimeAsString, + ) async { + if (changedProcessedStatus) { + final recipients = + await MatrixNotifications.instance.findNotificationRecipients( + session: session, + pupilNameAndGroup: pupilNameAndGroup, + tutor: tutor, + ); + unawaited(MatrixNotifications.instance.sendDirectTextMessage( + session: session, + recipients: recipients.toList(), + text: getSchooldayEventNotificationText( + eventcreator: modifiedBy, + pupilName: pupilNameAndGroup, + dateTimeAsString: dateTimeAsString, + schooldayEvent: schooldayEvent, + processedStatusChange: changedProcessedStatus, + ), + html: getSchooldayEventNotificationHtml( + eventcreator: modifiedBy, + pupilName: pupilNameAndGroup, + dateTimeAsString: dateTimeAsString, + schooldayEvent: schooldayEvent, + processedStatusChange: changedProcessedStatus), + )); + } // If processed is false We need to detach and delete the processed document if it exists - if (changedProcessedToFalse) { + if (changedProcessedStatus && schooldayEvent.processed == false) { if (schooldayEvent.processedDocumentId != null) { final file = await HubDocument.db .findById(session, schooldayEvent.processedDocumentId!); diff --git a/school_data_hub_server/lib/src/_features/timetable/endpoints/learning_group_endpoint.dart b/school_data_hub_server/lib/src/_features/timetable/endpoints/learning_group_endpoint.dart index df3bf393..a8fe2208 100644 --- a/school_data_hub_server/lib/src/_features/timetable/endpoints/learning_group_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/timetable/endpoints/learning_group_endpoint.dart @@ -10,13 +10,11 @@ class LearningGroupEndpoint extends Endpoint { Future createLessonGroup( Session session, LessonGroup lessonGroup) async { // Validate that the timetable exists if provided - if (lessonGroup.timetableId != null) { - final timetable = - await Timetable.db.findById(session, lessonGroup.timetableId!); - if (timetable == null) { - throw Exception( - 'Timetable with id ${lessonGroup.timetableId} does not exist.'); - } + final timetable = + await Timetable.db.findById(session, lessonGroup.timetableId); + if (timetable == null) { + throw Exception( + 'Timetable with id ${lessonGroup.timetableId} does not exist.'); } final lessonGroupInDatabase = diff --git a/school_data_hub_server/lib/src/_features/timetable/endpoints/scheduled_lesson_endpoint.dart b/school_data_hub_server/lib/src/_features/timetable/endpoints/scheduled_lesson_endpoint.dart index 5b07afeb..eb94e7bb 100644 --- a/school_data_hub_server/lib/src/_features/timetable/endpoints/scheduled_lesson_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/timetable/endpoints/scheduled_lesson_endpoint.dart @@ -10,53 +10,42 @@ class ScheduledLessonEndpoint extends Endpoint { Future createScheduledLesson( Session session, ScheduledLesson scheduledLesson) async { // Validate that the timetable exists - if (scheduledLesson.timetableId != null) { - final timetable = - await Timetable.db.findById(session, scheduledLesson.timetableId!); - if (timetable == null) { - throw Exception( - 'Timetable with id ${scheduledLesson.timetableId} does not exist.'); - } + final timetable = + await Timetable.db.findById(session, scheduledLesson.timetableId); + if (timetable == null) { + throw Exception( + 'Timetable with id ${scheduledLesson.timetableId} does not exist.'); } // Validate that the subject exists - if (scheduledLesson.subjectId != null) { - final subject = - await Subject.db.findById(session, scheduledLesson.subjectId!); - if (subject == null) { - throw Exception( - 'Subject with id ${scheduledLesson.subjectId} does not exist.'); - } + final subject = + await Subject.db.findById(session, scheduledLesson.subjectId); + if (subject == null) { + throw Exception( + 'Subject with id ${scheduledLesson.subjectId} does not exist.'); } // Validate that the room exists - if (scheduledLesson.roomId != null) { - final room = - await Classroom.db.findById(session, scheduledLesson.roomId!); - if (room == null) { - throw Exception( - 'Classroom with id ${scheduledLesson.roomId} does not exist.'); - } + final room = await Classroom.db.findById(session, scheduledLesson.roomId); + if (room == null) { + throw Exception( + 'Classroom with id ${scheduledLesson.roomId} does not exist.'); } // Validate that the lesson group exists - if (scheduledLesson.lessonGroupId != null) { - final lessonGroup = await LessonGroup.db - .findById(session, scheduledLesson.lessonGroupId!); - if (lessonGroup == null) { - throw Exception( - 'Lesson group with id ${scheduledLesson.lessonGroupId} does not exist.'); - } + final lessonGroup = + await LessonGroup.db.findById(session, scheduledLesson.lessonGroupId); + if (lessonGroup == null) { + throw Exception( + 'Lesson group with id ${scheduledLesson.lessonGroupId} does not exist.'); } // Validate that the timetable slot exists - if (scheduledLesson.scheduledAtId != null) { - final slot = await TimetableSlot.db - .findById(session, scheduledLesson.scheduledAtId!); - if (slot == null) { - throw Exception( - 'Timetable slot with id ${scheduledLesson.scheduledAtId} does not exist.'); - } + final slot = + await TimetableSlot.db.findById(session, scheduledLesson.scheduledAtId); + if (slot == null) { + throw Exception( + 'Timetable slot with id ${scheduledLesson.scheduledAtId} does not exist.'); } final scheduledLessonInDatabase = diff --git a/school_data_hub_server/lib/src/_features/timetable/endpoints/timetable_endpoint.dart b/school_data_hub_server/lib/src/_features/timetable/endpoints/timetable_endpoint.dart index d15ff234..969eacac 100644 --- a/school_data_hub_server/lib/src/_features/timetable/endpoints/timetable_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/timetable/endpoints/timetable_endpoint.dart @@ -10,13 +10,11 @@ class TimetableEndpoint extends Endpoint { Future createTimetable( Session session, Timetable timetable) async { // Validate that the school semester exists if provided - if (timetable.schoolSemesterId != null) { - final schoolSemester = await SchoolSemester.db - .findById(session, timetable.schoolSemesterId!); - if (schoolSemester == null) { - throw Exception( - 'School semester with id ${timetable.schoolSemesterId} does not exist.'); - } + final schoolSemester = + await SchoolSemester.db.findById(session, timetable.schoolSemesterId); + if (schoolSemester == null) { + throw Exception( + 'School semester with id ${timetable.schoolSemesterId} does not exist.'); } final timetableInDatabase = diff --git a/school_data_hub_server/lib/src/_features/timetable/endpoints/timetable_slot_endpoint.dart b/school_data_hub_server/lib/src/_features/timetable/endpoints/timetable_slot_endpoint.dart index c3a69c56..fe1dfe80 100644 --- a/school_data_hub_server/lib/src/_features/timetable/endpoints/timetable_slot_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/timetable/endpoints/timetable_slot_endpoint.dart @@ -10,13 +10,11 @@ class TimetableSlotEndpoint extends Endpoint { Future createTimetableSlot( Session session, TimetableSlot timetableSlot) async { // Validate that the timetable exists - if (timetableSlot.timetableId != null) { - final timetable = - await Timetable.db.findById(session, timetableSlot.timetableId!); - if (timetable == null) { - throw Exception( - 'Timetable with id ${timetableSlot.timetableId} does not exist.'); - } + final timetable = + await Timetable.db.findById(session, timetableSlot.timetableId); + if (timetable == null) { + throw Exception( + 'Timetable with id ${timetableSlot.timetableId} does not exist.'); } final timetableSlotInDatabase = diff --git a/school_data_hub_server/lib/src/_features/user/endpoints/user_endpoints.dart b/school_data_hub_server/lib/src/_features/user/endpoints/user_endpoints.dart index 5860a566..f98acb51 100644 --- a/school_data_hub_server/lib/src/_features/user/endpoints/user_endpoints.dart +++ b/school_data_hub_server/lib/src/_features/user/endpoints/user_endpoints.dart @@ -119,34 +119,6 @@ class UserEndpoint extends Endpoint { } final result = await Emails.changePassword( session, authenticationInfo.userId, oldPassword, newPassword); - // // Find the user's email auth entry - // var emailAuth = await EmailAuth.db.findFirstRow(session, where: (t) { - // return t.userId.equals(authenticationInfo.userId); - // }); - - // if (emailAuth == null) { - // _log.severe('User doesn\'t have email authentication'); - // return false; // User doesn't have email authentication - // } - - // // Generate hash for the old password and compare with stored hash - // String oldPasswordHash = await Emails.generatePasswordHash(oldPassword); - // bool isValid = (oldPasswordHash == emailAuth.hash); - // _log.info('oldPasswordHash: $oldPasswordHash'); - // _log.info('emailAuth.hash: ${emailAuth.hash}'); - // _log.info('isValid: $isValid'); - // if (!isValid) { - // _log.severe('Old password is incorrect'); - // return false; // Old password is incorrect - // } - - // // Generate hash for the new password - // emailAuth.hash = await Emails.generatePasswordHash(newPassword); - // _log.info('newPasswordHash: ${emailAuth.hash}'); - - // // Update the password in the database - // await EmailAuth.db.updateRow(session, emailAuth); - // _log.info('Password changed successfully'); return result; } diff --git a/school_data_hub_server/lib/src/_features/user/models/staff_user.spy.yaml b/school_data_hub_server/lib/src/_features/user/models/staff_user.spy.yaml index 0af35c2e..f4528873 100644 --- a/school_data_hub_server/lib/src/_features/user/models/staff_user.spy.yaml +++ b/school_data_hub_server/lib/src/_features/user/models/staff_user.spy.yaml @@ -4,11 +4,15 @@ table: user fields: userInfo: module:auth:UserInfo?, relation(onDelete=Cascade) role: Role + matrixUserId: String? timeUnits: int reliefTimeUnits: int scheduledLessonsTeacher: List?, relation(name=user_scheduled_lesson) lessonsTeacher: List?, relation(name=user_lesson) pupilsAuth: Set? + ## DEPRECATED: schoolday events processing team is deprecated and will be removed in the future + ## Use scopes instead + schooldayEventsProcessingTeam: String? credit: int userFlags: UserFlags indexes: diff --git a/school_data_hub_server/lib/src/generated/_features/user/models/staff_user.dart b/school_data_hub_server/lib/src/generated/_features/user/models/staff_user.dart index 23cec023..7bc55aa5 100644 --- a/school_data_hub_server/lib/src/generated/_features/user/models/staff_user.dart +++ b/school_data_hub_server/lib/src/generated/_features/user/models/staff_user.dart @@ -26,11 +26,13 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { required this.userInfoId, this.userInfo, required this.role, + this.matrixUserId, required this.timeUnits, required this.reliefTimeUnits, this.scheduledLessonsTeacher, this.lessonsTeacher, this.pupilsAuth, + this.schooldayEventsProcessingTeam, required this.credit, required this.userFlags, }); @@ -40,11 +42,13 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { required int userInfoId, _i2.UserInfo? userInfo, required _i3.Role role, + String? matrixUserId, required int timeUnits, required int reliefTimeUnits, List<_i4.ScheduledLessonTeacher>? scheduledLessonsTeacher, List<_i5.LessonTeacher>? lessonsTeacher, Set? pupilsAuth, + String? schooldayEventsProcessingTeam, required int credit, required _i6.UserFlags userFlags, }) = _UserImpl; @@ -58,6 +62,7 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { : _i2.UserInfo.fromJson( (jsonSerialization['userInfo'] as Map)), role: _i3.Role.fromJson((jsonSerialization['role'] as String)), + matrixUserId: jsonSerialization['matrixUserId'] as String?, timeUnits: jsonSerialization['timeUnits'] as int, reliefTimeUnits: jsonSerialization['reliefTimeUnits'] as int, scheduledLessonsTeacher: (jsonSerialization['scheduledLessonsTeacher'] @@ -73,6 +78,8 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { : _i1.SetJsonExtension.fromJson( (jsonSerialization['pupilsAuth'] as List), itemFromJson: (e) => e as int), + schooldayEventsProcessingTeam: + jsonSerialization['schooldayEventsProcessingTeam'] as String?, credit: jsonSerialization['credit'] as int, userFlags: _i6.UserFlags.fromJson( (jsonSerialization['userFlags'] as Map)), @@ -92,6 +99,8 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { _i3.Role role; + String? matrixUserId; + int timeUnits; int reliefTimeUnits; @@ -102,6 +111,8 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { Set? pupilsAuth; + String? schooldayEventsProcessingTeam; + int credit; _i6.UserFlags userFlags; @@ -117,11 +128,13 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { int? userInfoId, _i2.UserInfo? userInfo, _i3.Role? role, + String? matrixUserId, int? timeUnits, int? reliefTimeUnits, List<_i4.ScheduledLessonTeacher>? scheduledLessonsTeacher, List<_i5.LessonTeacher>? lessonsTeacher, Set? pupilsAuth, + String? schooldayEventsProcessingTeam, int? credit, _i6.UserFlags? userFlags, }); @@ -132,6 +145,7 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { 'userInfoId': userInfoId, if (userInfo != null) 'userInfo': userInfo?.toJson(), 'role': role.toJson(), + if (matrixUserId != null) 'matrixUserId': matrixUserId, 'timeUnits': timeUnits, 'reliefTimeUnits': reliefTimeUnits, if (scheduledLessonsTeacher != null) @@ -141,6 +155,8 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { 'lessonsTeacher': lessonsTeacher?.toJson(valueToJson: (v) => v.toJson()), if (pupilsAuth != null) 'pupilsAuth': pupilsAuth?.toJson(), + if (schooldayEventsProcessingTeam != null) + 'schooldayEventsProcessingTeam': schooldayEventsProcessingTeam, 'credit': credit, 'userFlags': userFlags.toJson(), }; @@ -153,6 +169,7 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { 'userInfoId': userInfoId, if (userInfo != null) 'userInfo': userInfo?.toJsonForProtocol(), 'role': role.toJson(), + if (matrixUserId != null) 'matrixUserId': matrixUserId, 'timeUnits': timeUnits, 'reliefTimeUnits': reliefTimeUnits, if (scheduledLessonsTeacher != null) @@ -162,6 +179,8 @@ abstract class User implements _i1.TableRow, _i1.ProtocolSerialization { 'lessonsTeacher': lessonsTeacher?.toJson(valueToJson: (v) => v.toJsonForProtocol()), if (pupilsAuth != null) 'pupilsAuth': pupilsAuth?.toJson(), + if (schooldayEventsProcessingTeam != null) + 'schooldayEventsProcessingTeam': schooldayEventsProcessingTeam, 'credit': credit, 'userFlags': userFlags.toJsonForProtocol(), }; @@ -213,11 +232,13 @@ class _UserImpl extends User { required int userInfoId, _i2.UserInfo? userInfo, required _i3.Role role, + String? matrixUserId, required int timeUnits, required int reliefTimeUnits, List<_i4.ScheduledLessonTeacher>? scheduledLessonsTeacher, List<_i5.LessonTeacher>? lessonsTeacher, Set? pupilsAuth, + String? schooldayEventsProcessingTeam, required int credit, required _i6.UserFlags userFlags, }) : super._( @@ -225,11 +246,13 @@ class _UserImpl extends User { userInfoId: userInfoId, userInfo: userInfo, role: role, + matrixUserId: matrixUserId, timeUnits: timeUnits, reliefTimeUnits: reliefTimeUnits, scheduledLessonsTeacher: scheduledLessonsTeacher, lessonsTeacher: lessonsTeacher, pupilsAuth: pupilsAuth, + schooldayEventsProcessingTeam: schooldayEventsProcessingTeam, credit: credit, userFlags: userFlags, ); @@ -243,11 +266,13 @@ class _UserImpl extends User { int? userInfoId, Object? userInfo = _Undefined, _i3.Role? role, + Object? matrixUserId = _Undefined, int? timeUnits, int? reliefTimeUnits, Object? scheduledLessonsTeacher = _Undefined, Object? lessonsTeacher = _Undefined, Object? pupilsAuth = _Undefined, + Object? schooldayEventsProcessingTeam = _Undefined, int? credit, _i6.UserFlags? userFlags, }) { @@ -257,6 +282,7 @@ class _UserImpl extends User { userInfo: userInfo is _i2.UserInfo? ? userInfo : this.userInfo?.copyWith(), role: role ?? this.role, + matrixUserId: matrixUserId is String? ? matrixUserId : this.matrixUserId, timeUnits: timeUnits ?? this.timeUnits, reliefTimeUnits: reliefTimeUnits ?? this.reliefTimeUnits, scheduledLessonsTeacher: scheduledLessonsTeacher @@ -269,6 +295,9 @@ class _UserImpl extends User { pupilsAuth: pupilsAuth is Set? ? pupilsAuth : this.pupilsAuth?.map((e0) => e0).toSet(), + schooldayEventsProcessingTeam: schooldayEventsProcessingTeam is String? + ? schooldayEventsProcessingTeam + : this.schooldayEventsProcessingTeam, credit: credit ?? this.credit, userFlags: userFlags ?? this.userFlags.copyWith(), ); @@ -286,6 +315,10 @@ class UserTable extends _i1.Table { this, _i1.EnumSerialization.byName, ); + matrixUserId = _i1.ColumnString( + 'matrixUserId', + this, + ); timeUnits = _i1.ColumnInt( 'timeUnits', this, @@ -298,6 +331,10 @@ class UserTable extends _i1.Table { 'pupilsAuth', this, ); + schooldayEventsProcessingTeam = _i1.ColumnString( + 'schooldayEventsProcessingTeam', + this, + ); credit = _i1.ColumnInt( 'credit', this, @@ -314,6 +351,8 @@ class UserTable extends _i1.Table { late final _i1.ColumnEnum<_i3.Role> role; + late final _i1.ColumnString matrixUserId; + late final _i1.ColumnInt timeUnits; late final _i1.ColumnInt reliefTimeUnits; @@ -328,6 +367,8 @@ class UserTable extends _i1.Table { late final _i1.ColumnSerializable pupilsAuth; + late final _i1.ColumnString schooldayEventsProcessingTeam; + late final _i1.ColumnInt credit; late final _i1.ColumnSerializable userFlags; @@ -414,9 +455,11 @@ class UserTable extends _i1.Table { id, userInfoId, role, + matrixUserId, timeUnits, reliefTimeUnits, pupilsAuth, + schooldayEventsProcessingTeam, credit, userFlags, ]; diff --git a/school_data_hub_server/lib/src/generated/endpoints.dart b/school_data_hub_server/lib/src/generated/endpoints.dart index 089fb722..ba00aee7 100644 --- a/school_data_hub_server/lib/src/generated/endpoints.dart +++ b/school_data_hub_server/lib/src/generated/endpoints.dart @@ -104,39 +104,41 @@ import 'package:school_data_hub_server/src/generated/_features/pupil/models/pupi as _i59; import 'package:school_data_hub_server/src/generated/_features/learning_support/models/support_level.dart' as _i60; -import 'package:school_data_hub_server/src/generated/_features/school_data/models/school_data.dart' +import 'package:school_data_hub_server/src/generated/_features/pupil/models/pupil_data/after_school_care/after_school_care.dart' as _i61; -import 'package:school_data_hub_server/src/generated/_features/school_lists/models/pupil_entry.dart' +import 'package:school_data_hub_server/src/generated/_features/school_data/models/school_data.dart' as _i62; -import 'package:school_data_hub_server/src/generated/_features/schoolday/models/school_semester.dart' +import 'package:school_data_hub_server/src/generated/_features/school_lists/models/pupil_entry.dart' as _i63; -import 'package:school_data_hub_server/src/generated/_features/schoolday/models/schoolday.dart' +import 'package:school_data_hub_server/src/generated/_features/schoolday/models/school_semester.dart' as _i64; -import 'package:school_data_hub_server/src/generated/_features/schoolday_events/models/schoolday_event_type.dart' +import 'package:school_data_hub_server/src/generated/_features/schoolday/models/schoolday.dart' as _i65; -import 'package:school_data_hub_server/src/generated/_features/schoolday_events/models/schoolday_event.dart' +import 'package:school_data_hub_server/src/generated/_features/schoolday_events/models/schoolday_event_type.dart' as _i66; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/classroom.dart' +import 'package:school_data_hub_server/src/generated/_features/schoolday_events/models/schoolday_event.dart' as _i67; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/lesson/lesson_group.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/classroom.dart' as _i68; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/scheduled_lesson.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/lesson/lesson_group.dart' as _i69; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/lesson_group_membership.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/scheduled_lesson.dart' as _i70; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/subject.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/lesson_group_membership.dart' as _i71; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/timetable.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/subject.dart' as _i72; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/timetable_slot.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/timetable.dart' as _i73; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/weekday_enum.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/timetable_slot.dart' as _i74; -import 'package:school_data_hub_server/src/generated/_features/workbooks/models/pupil_workbook.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/weekday_enum.dart' as _i75; -import 'package:school_data_hub_server/src/generated/_features/workbooks/models/workbook.dart' +import 'package:school_data_hub_server/src/generated/_features/workbooks/models/pupil_workbook.dart' as _i76; -import 'package:serverpod_auth_server/serverpod_auth_server.dart' as _i77; +import 'package:school_data_hub_server/src/generated/_features/workbooks/models/workbook.dart' + as _i77; +import 'package:serverpod_auth_server/serverpod_auth_server.dart' as _i78; class Endpoints extends _i1.EndpointDispatch { @override @@ -406,6 +408,21 @@ class Endpoints extends _i1.EndpointDispatch { type: _i1.getType(), nullable: false, ), + 'schooldayEventsProcessingTeam': _i1.ParameterDescription( + name: 'schooldayEventsProcessingTeam', + type: _i1.getType(), + nullable: true, + ), + 'matrixUserId': _i1.ParameterDescription( + name: 'matrixUserId', + type: _i1.getType(), + nullable: true, + ), + 'credit': _i1.ParameterDescription( + name: 'credit', + type: _i1.getType(), + nullable: true, + ), }, call: ( _i1.Session session, @@ -422,6 +439,10 @@ class Endpoints extends _i1.EndpointDispatch { reliefTimeUnits: params['reliefTimeUnits'], scopeNames: params['scopeNames'], isTester: params['isTester'], + schooldayEventsProcessingTeam: + params['schooldayEventsProcessingTeam'], + matrixUserId: params['matrixUserId'], + credit: params['credit'], ), ), 'resetPassword': _i1.MethodConnector( @@ -2955,6 +2976,31 @@ class Endpoints extends _i1.EndpointDispatch { params['schoolyearHeldBackDate'], ), ), + 'updateAfterSchoolCare': _i1.MethodConnector( + name: 'updateAfterSchoolCare', + params: { + 'pupilId': _i1.ParameterDescription( + name: 'pupilId', + type: _i1.getType(), + nullable: false, + ), + 'afterSchoolCare': _i1.ParameterDescription( + name: 'afterSchoolCare', + type: _i1.getType<_i61.AfterSchoolCare>(), + nullable: false, + ), + }, + call: ( + _i1.Session session, + Map params, + ) async => + (endpoints['pupilUpdate'] as _i20.PupilUpdateEndpoint) + .updateAfterSchoolCare( + session, + params['pupilId'], + params['afterSchoolCare'], + ), + ), }, ); connectors['schoolData'] = _i1.EndpointConnector( @@ -2966,7 +3012,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'schoolData': _i1.ParameterDescription( name: 'schoolData', - type: _i1.getType<_i61.SchoolData>(), + type: _i1.getType<_i62.SchoolData>(), nullable: false, ) }, @@ -3132,7 +3178,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'entry': _i1.ParameterDescription( name: 'entry', - type: _i1.getType<_i62.PupilListEntry>(), + type: _i1.getType<_i63.PupilListEntry>(), nullable: false, ) }, @@ -3238,7 +3284,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'schoolSemester': _i1.ParameterDescription( name: 'schoolSemester', - type: _i1.getType<_i63.SchoolSemester>(), + type: _i1.getType<_i64.SchoolSemester>(), nullable: false, ) }, @@ -3257,7 +3303,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'semester': _i1.ParameterDescription( name: 'semester', - type: _i1.getType<_i63.SchoolSemester>(), + type: _i1.getType<_i64.SchoolSemester>(), nullable: false, ) }, @@ -3333,7 +3379,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'schoolday': _i1.ParameterDescription( name: 'schoolday', - type: _i1.getType<_i64.Schoolday>(), + type: _i1.getType<_i65.Schoolday>(), nullable: false, ) }, @@ -3397,6 +3443,16 @@ class Endpoints extends _i1.EndpointDispatch { type: _i1.getType(), nullable: false, ), + 'pupilNameAndGroup': _i1.ParameterDescription( + name: 'pupilNameAndGroup', + type: _i1.getType(), + nullable: false, + ), + 'dateTimeAsString': _i1.ParameterDescription( + name: 'dateTimeAsString', + type: _i1.getType(), + nullable: false, + ), 'schooldayId': _i1.ParameterDescription( name: 'schooldayId', type: _i1.getType(), @@ -3404,7 +3460,7 @@ class Endpoints extends _i1.EndpointDispatch { ), 'type': _i1.ParameterDescription( name: 'type', - type: _i1.getType<_i65.SchooldayEventType>(), + type: _i1.getType<_i66.SchooldayEventType>(), nullable: false, ), 'reason': _i1.ParameterDescription( @@ -3417,6 +3473,11 @@ class Endpoints extends _i1.EndpointDispatch { type: _i1.getType(), nullable: false, ), + 'tutor': _i1.ParameterDescription( + name: 'tutor', + type: _i1.getType(), + nullable: false, + ), }, call: ( _i1.Session session, @@ -3426,10 +3487,13 @@ class Endpoints extends _i1.EndpointDispatch { .createSchooldayEvent( session, pupilId: params['pupilId'], + pupilNameAndGroup: params['pupilNameAndGroup'], + dateTimeAsString: params['dateTimeAsString'], schooldayId: params['schooldayId'], type: params['type'], reason: params['reason'], createdBy: params['createdBy'], + tutor: params['tutor'], ), ), 'updateSchooldayEvent': _i1.MethodConnector( @@ -3437,14 +3501,34 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'schooldayEvent': _i1.ParameterDescription( name: 'schooldayEvent', - type: _i1.getType<_i66.SchooldayEvent>(), + type: _i1.getType<_i67.SchooldayEvent>(), nullable: false, ), - 'changedProcessedToFalse': _i1.ParameterDescription( - name: 'changedProcessedToFalse', + 'changedProcessedStatus': _i1.ParameterDescription( + name: 'changedProcessedStatus', type: _i1.getType(), nullable: false, ), + 'pupilNameAndGroup': _i1.ParameterDescription( + name: 'pupilNameAndGroup', + type: _i1.getType(), + nullable: false, + ), + 'tutor': _i1.ParameterDescription( + name: 'tutor', + type: _i1.getType(), + nullable: false, + ), + 'modifiedBy': _i1.ParameterDescription( + name: 'modifiedBy', + type: _i1.getType(), + nullable: false, + ), + 'dateTimeAsString': _i1.ParameterDescription( + name: 'dateTimeAsString', + type: _i1.getType(), + nullable: false, + ), }, call: ( _i1.Session session, @@ -3454,7 +3538,11 @@ class Endpoints extends _i1.EndpointDispatch { .updateSchooldayEvent( session, params['schooldayEvent'], - params['changedProcessedToFalse'], + params['changedProcessedStatus'], + params['pupilNameAndGroup'], + params['tutor'], + params['modifiedBy'], + params['dateTimeAsString'], ), ), 'deleteSchooldayEvent': _i1.MethodConnector( @@ -3549,7 +3637,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'classroom': _i1.ParameterDescription( name: 'classroom', - type: _i1.getType<_i67.Classroom>(), + type: _i1.getType<_i68.Classroom>(), nullable: false, ) }, @@ -3635,7 +3723,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'classroom': _i1.ParameterDescription( name: 'classroom', - type: _i1.getType<_i67.Classroom>(), + type: _i1.getType<_i68.Classroom>(), nullable: false, ) }, @@ -3679,7 +3767,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'lessonGroup': _i1.ParameterDescription( name: 'lessonGroup', - type: _i1.getType<_i68.LessonGroup>(), + type: _i1.getType<_i69.LessonGroup>(), nullable: false, ) }, @@ -3803,7 +3891,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'lessonGroup': _i1.ParameterDescription( name: 'lessonGroup', - type: _i1.getType<_i68.LessonGroup>(), + type: _i1.getType<_i69.LessonGroup>(), nullable: false, ) }, @@ -3847,7 +3935,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'scheduledLesson': _i1.ParameterDescription( name: 'scheduledLesson', - type: _i1.getType<_i69.ScheduledLesson>(), + type: _i1.getType<_i70.ScheduledLesson>(), nullable: false, ) }, @@ -3981,7 +4069,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'scheduledLesson': _i1.ParameterDescription( name: 'scheduledLesson', - type: _i1.getType<_i69.ScheduledLesson>(), + type: _i1.getType<_i70.ScheduledLesson>(), nullable: false, ) }, @@ -4044,7 +4132,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'membership': _i1.ParameterDescription( name: 'membership', - type: _i1.getType<_i70.ScheduledLessonGroupMembership>(), + type: _i1.getType<_i71.ScheduledLessonGroupMembership>(), nullable: false, ) }, @@ -4161,7 +4249,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'membership': _i1.ParameterDescription( name: 'membership', - type: _i1.getType<_i70.ScheduledLessonGroupMembership>(), + type: _i1.getType<_i71.ScheduledLessonGroupMembership>(), nullable: false, ) }, @@ -4259,7 +4347,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'subject': _i1.ParameterDescription( name: 'subject', - type: _i1.getType<_i71.Subject>(), + type: _i1.getType<_i72.Subject>(), nullable: false, ) }, @@ -4362,7 +4450,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'subject': _i1.ParameterDescription( name: 'subject', - type: _i1.getType<_i71.Subject>(), + type: _i1.getType<_i72.Subject>(), nullable: false, ) }, @@ -4404,7 +4492,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'timetable': _i1.ParameterDescription( name: 'timetable', - type: _i1.getType<_i72.Timetable>(), + type: _i1.getType<_i73.Timetable>(), nullable: false, ) }, @@ -4501,7 +4589,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'timetable': _i1.ParameterDescription( name: 'timetable', - type: _i1.getType<_i72.Timetable>(), + type: _i1.getType<_i73.Timetable>(), nullable: false, ) }, @@ -4564,7 +4652,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'timetableSlot': _i1.ParameterDescription( name: 'timetableSlot', - type: _i1.getType<_i73.TimetableSlot>(), + type: _i1.getType<_i74.TimetableSlot>(), nullable: false, ) }, @@ -4631,7 +4719,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'day': _i1.ParameterDescription( name: 'day', - type: _i1.getType<_i74.Weekday>(), + type: _i1.getType<_i75.Weekday>(), nullable: false, ) }, @@ -4650,7 +4738,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'timetableSlot': _i1.ParameterDescription( name: 'timetableSlot', - type: _i1.getType<_i73.TimetableSlot>(), + type: _i1.getType<_i74.TimetableSlot>(), nullable: false, ) }, @@ -4812,7 +4900,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'pupilWorkbook': _i1.ParameterDescription( name: 'pupilWorkbook', - type: _i1.getType<_i75.PupilWorkbook>(), + type: _i1.getType<_i76.PupilWorkbook>(), nullable: false, ) }, @@ -4856,7 +4944,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'workbook': _i1.ParameterDescription( name: 'workbook', - type: _i1.getType<_i76.Workbook>(), + type: _i1.getType<_i77.Workbook>(), nullable: false, ) }, @@ -4903,7 +4991,7 @@ class Endpoints extends _i1.EndpointDispatch { params: { 'workbook': _i1.ParameterDescription( name: 'workbook', - type: _i1.getType<_i76.Workbook>(), + type: _i1.getType<_i77.Workbook>(), nullable: false, ) }, @@ -5026,6 +5114,6 @@ class Endpoints extends _i1.EndpointDispatch { ), }, ); - modules['serverpod_auth'] = _i77.Endpoints()..initializeEndpoints(server); + modules['serverpod_auth'] = _i78.Endpoints()..initializeEndpoints(server); } } diff --git a/school_data_hub_server/lib/src/generated/protocol.dart b/school_data_hub_server/lib/src/generated/protocol.dart index 79ab6b8e..901e52f9 100644 --- a/school_data_hub_server/lib/src/generated/protocol.dart +++ b/school_data_hub_server/lib/src/generated/protocol.dart @@ -4638,6 +4638,12 @@ class Protocol extends _i1.SerializationManagerServer { isNullable: false, dartType: 'protocol:Role', ), + _i2.ColumnDefinition( + name: 'matrixUserId', + columnType: _i2.ColumnType.text, + isNullable: true, + dartType: 'String?', + ), _i2.ColumnDefinition( name: 'timeUnits', columnType: _i2.ColumnType.bigint, @@ -4656,6 +4662,12 @@ class Protocol extends _i1.SerializationManagerServer { isNullable: true, dartType: 'Set?', ), + _i2.ColumnDefinition( + name: 'schooldayEventsProcessingTeam', + columnType: _i2.ColumnType.text, + isNullable: true, + dartType: 'String?', + ), _i2.ColumnDefinition( name: 'credit', columnType: _i2.ColumnType.bigint, diff --git a/school_data_hub_server/lib/src/generated/protocol.yaml b/school_data_hub_server/lib/src/generated/protocol.yaml index c9d6f87c..6c763005 100644 --- a/school_data_hub_server/lib/src/generated/protocol.yaml +++ b/school_data_hub_server/lib/src/generated/protocol.yaml @@ -127,6 +127,7 @@ pupilUpdate: - updatePublicMediaAuth: - updateSupportLevel: - updateSchoolyearHeldBackDate: + - updateAfterSchoolCare: schoolData: - postSchoolData: - getSchoolData: diff --git a/school_data_hub_server/lib/src/utils/mailer.dart b/school_data_hub_server/lib/src/utils/mailer.dart index b318fab7..ff0f198d 100644 --- a/school_data_hub_server/lib/src/utils/mailer.dart +++ b/school_data_hub_server/lib/src/utils/mailer.dart @@ -13,6 +13,7 @@ class MailerService { MailerService._internal(); final _logger = Logger('MailerService'); + bool _isInitialized = false; late final String _username; late final String _password; late final String _smtpHost; @@ -35,20 +36,32 @@ class MailerService { _smtpPort = smtpPort; _fromName = fromName; _defaultRecipient = defaultRecipient; + _isInitialized = true; } /// Initialize from Serverpod session (recommended approach) - void initializeFromSession(Session session) { + /// Returns true if initialization was successful, false otherwise + bool initializeFromSession(Session session) { final passwords = session.passwords; + final username = passwords['emailUsername'] ?? ''; + final password = passwords['emailPassword'] ?? ''; + final smtpHost = passwords['emailSmtpHost'] ?? ''; + + // Validate that required fields are present + if (username.isEmpty || password.isEmpty || smtpHost.isEmpty) { + return false; + } + initialize( - username: passwords['emailUsername'] ?? '', - password: passwords['emailPassword'] ?? '', - smtpHost: passwords['emailSmtpHost'] ?? '', + username: username, + password: password, + smtpHost: smtpHost, smtpPort: int.tryParse(passwords['emailSmtpPort'] ?? '0') ?? 587, fromName: 'Schuldaten Benachrichtigungen', defaultRecipient: '', ); + return true; } /// Send an email with the specified parameters @@ -61,6 +74,13 @@ class MailerService { List? bccRecipients, List? attachments, }) async { + if (!_isInitialized) { + _logger.severe( + 'MailerService has not been initialized. Call initialize() or initializeFromSession() first.', + ); + return false; + } + try { final smtpServer = SmtpServer( _smtpHost, diff --git a/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_client.dart b/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_client.dart new file mode 100644 index 00000000..2d720839 --- /dev/null +++ b/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_client.dart @@ -0,0 +1,46 @@ +import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; +import 'package:serverpod/serverpod.dart'; + +typedef MatrixResponse = http.Response; + +class MatrixClient { + MatrixClient() : _httpClient = http.Client(); + + final http.Client _httpClient; + final _matrixUrl = Serverpod.instance.getPassword('matrixServerUrl'); + final _authToken = Serverpod.instance.getPassword('matrixAuthToken'); + + final _log = Logger('MatrixClient'); + + /// HTTP headers used for all Matrix API requests + Map get headers => { + 'Authorization': 'Bearer $_authToken', + 'Content-Type': 'application/json', + }; + + Future get(String endpoint) async { + final uri = Uri.parse('$_matrixUrl$endpoint'); + return await _httpClient.get(uri, headers: headers); + } + + Future post(String endpoint, String body) async { + final uri = Uri.parse('$_matrixUrl$endpoint'); + return await _httpClient.post(uri, headers: headers, body: body); + } + + Future put(String endpoint, String body) async { + final uri = Uri.parse('$_matrixUrl$endpoint'); + return await _httpClient.put(uri, headers: headers, body: body); + } + + Future delete(String endpoint) async { + final uri = Uri.parse('$_matrixUrl$endpoint'); + return await _httpClient.delete(uri, headers: headers); + } + + // Clean up when done (call on app shutdown) + void dispose() { + _httpClient.close(); + } +} diff --git a/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_helper.dart b/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_helper.dart new file mode 100644 index 00000000..6374908c --- /dev/null +++ b/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_helper.dart @@ -0,0 +1,20 @@ +class MatrixHelper { + /// Generates a unique transaction ID for message deduplication + static String generateTransactionId() { + return DateTime.now().millisecondsSinceEpoch.toString(); + } + + /// URL encodes a Matrix room ID for API calls + static String encodeRoomId(String roomId) { + return roomId.replaceAllMapped(RegExp(r'[!:]'), (match) { + switch (match.group(0)) { + case '!': + return '%21'; + case ':': + return '%3A'; + default: + return match.group(0)!; + } + }); + } +} diff --git a/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_models.dart b/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_models.dart new file mode 100644 index 00000000..631d0174 --- /dev/null +++ b/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_models.dart @@ -0,0 +1,29 @@ +class MatrixMessage { + final String msgtype; + final String body; + final String? format; + final String? formattedBody; + + const MatrixMessage({ + required this.msgtype, + required this.body, + this.format, + this.formattedBody, + }); + + factory MatrixMessage.fromJson(Map json) => MatrixMessage( + msgtype: json['msgtype'] as String, + body: json['body'] as String, + format: json['format'] as String?, + formattedBody: json['formatted_body'] as String?, + ); +} + +class MatrixMessageResponse { + final String eventId; + + MatrixMessageResponse({required this.eventId}); + factory MatrixMessageResponse.fromJson(Map json) { + return MatrixMessageResponse(eventId: json['event_id'] as String); + } +} diff --git a/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_notifications.dart b/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_notifications.dart new file mode 100644 index 00000000..df68508a --- /dev/null +++ b/school_data_hub_server/lib/src/utils/matrix_notifications/matrix_notifications.dart @@ -0,0 +1,491 @@ +import 'dart:convert'; + +import 'package:logging/logging.dart'; +import 'package:school_data_hub_server/src/generated/protocol.dart'; +import 'package:school_data_hub_server/src/utils/matrix_notifications/matrix_client.dart'; +import 'package:school_data_hub_server/src/utils/matrix_notifications/matrix_helper.dart'; +import 'package:school_data_hub_server/src/utils/matrix_notifications/matrix_models.dart'; +import 'package:serverpod/serverpod.dart'; +import 'package:serverpod_auth_server/serverpod_auth_server.dart'; + +class MatrixNotifications { + static MatrixNotifications? _instance; + static MatrixNotifications get instance { + _instance ??= MatrixNotifications._internal(); + return _instance!; + } + + MatrixNotifications._internal(); + + final _currentUserId = '@schuldaten-hub:hermannschule.de'; + final MatrixClient _matrixClient = MatrixClient(); + final _log = Logger('MatrixNotifications'); + + //- SEND MESSAGE + + /// Sends a message to a Matrix room using the notifications account + Future sendMessage({ + required MatrixMessage message, + required String roomId, + }) async { + try { + final transactionId = MatrixHelper.generateTransactionId(); + final encodedRoomId = MatrixHelper.encodeRoomId(roomId); + final encodedTransactionId = Uri.encodeComponent(transactionId); + final endpoint = + '/_matrix/client/v3/rooms/$encodedRoomId/send/m.room.message/$encodedTransactionId'; + + final messageData = { + 'msgtype': message.msgtype, + 'body': message.body, + }; + + // Add formatted body if format and formattedBody are provided + if (message.format != null && message.formattedBody != null) { + messageData['format'] = message.format; + messageData['formatted_body'] = message.formattedBody; + } + + final response = + await _matrixClient.put(endpoint, jsonEncode(messageData)); + + if (response.statusCode != 200) { + throw Exception( + 'Failed to send message: ${response.statusCode} - ${response.body}', + ); + } + + return MatrixMessageResponse.fromJson(jsonDecode(response.body)); + } catch (e, stackTrace) { + _log.severe('Failed to send message', e, stackTrace); + rethrow; + } + } + + /// Sends a direct text message to multiple recipients with HTML formatting + /// Finds recipients by matching tutor username or scope names containing team name character + Future sendDirectTextMessage({ + required Session session, + required List recipients, + required String text, + String? html, + }) async { + if (recipients.isEmpty) { + _log.warning('No recipients provided, skipping notification'); + return; + } + + _log.info('Sending direct text message to ${recipients.length} recipients'); + + for (final recipient in recipients) { + try { + await _sendDirectTextMessageToSingleUser( + targetUserId: recipient, + text: text, + html: html, + ); + _log.info('Successfully sent message to $recipient'); + } catch (e, stackTrace) { + _log.severe( + 'Failed to send message to $recipient', + e, + stackTrace, + ); + // Continue sending to other recipients even if one fails + } + } + } + + /// Finds notification recipients based on tutor and team name from pupil name + /// Returns list of Matrix user IDs (usernames) that should receive notifications + Future> findNotificationRecipients({ + required Session session, + required String pupilNameAndGroup, + required String tutor, + }) async { + // Extract the group name from the pupilNameAndGroup string + // that is everything between the first and last parenthesis + final schooldayEventManagementTeamName = + pupilNameAndGroup.split('(')[1].split(')')[0].substring(0, 1); + + // Fetch all users with their UserInfo, then filter in memory + // because Serverpod's query builder doesn't support .any() on list fields + final allUsers = await User.db + .find(session, include: User.include(userInfo: UserInfo.include())); + + final notificationRecipients = allUsers.where((user) { + // Check if user is the tutor + if (user.userInfo?.userName == tutor) { + return true; + } + // Check if any scope name contains the team name character + final scopeNames = user.userInfo?.scopeNames ?? []; + return scopeNames.any((scope) => + scope.contains('SchooldayEventsManagement.') && + scope.split('.').last.contains(schooldayEventManagementTeamName)); + }).toList(); + + // Convert to list of user names (Matrix user IDs) + return notificationRecipients + .map((user) => user.matrixUserId!) + .whereType() + .toSet(); + } + + /// Internal method to send a direct text message to a single user + Future _sendDirectTextMessageToSingleUser({ + required String targetUserId, + required String text, + String? html, + }) async { + try { + _log.info('Sending direct text message to $targetUserId'); + + final roomId = await _findOrCreateDirectMessageRoom( + targetUserId: targetUserId, + currentUserId: _currentUserId, + ); + + _log.info('Using room ID: $roomId'); + + final response = await sendMessage( + message: MatrixMessage( + msgtype: 'm.text', + body: text, // Plain text fallback + format: html != null ? 'org.matrix.custom.html' : null, + formattedBody: html, + ), + roomId: roomId, + ); + + _log.info('Message sent successfully with event ID: ${response.eventId}'); + return response; + } catch (e, stackTrace) { + _log.severe('Failed to send direct text message', e, stackTrace); + rethrow; + } + } + + //- ROOM MANAGEMENT + + /// Finds or creates a direct message room with a specific user + Future _findOrCreateDirectMessageRoom({ + required String targetUserId, + required String currentUserId, + }) async { + try { + _log.info('Finding or creating direct message room for: $targetUserId'); + + final existingRoomId = await _findExistingDirectMessageRoom( + targetUserId: targetUserId, + currentUserId: currentUserId, + ); + + if (existingRoomId != null) { + _log.info('Found existing room: $existingRoomId'); + return existingRoomId; + } + + _log.info('No existing room found, creating new direct message room'); + return await _createDirectMessageRoom( + targetUserId: targetUserId, + currentUserId: currentUserId, + ); + } catch (e, stackTrace) { + _log.severe( + 'Failed to find or create direct message room', e, stackTrace); + rethrow; + } + } + + /// Creates a new direct message room with specific power levels + Future _createDirectMessageRoom({ + required String targetUserId, + required String currentUserId, + }) async { + try { + final roomData = { + 'is_direct': true, + 'name': 'Schuldaten Benachrichtigungen', + 'invite': [targetUserId], + 'preset': 'trusted_private_chat', + 'creation_content': {'m.federate': false}, + 'initial_state': [ + { + 'type': 'm.room.encryption', + 'content': { + 'algorithm': 'm.megolm.v1.aes-sha2', + }, + }, + ], + 'power_level_content_override': { + 'users': { + currentUserId: 100, // Admin has full permissions + targetUserId: 0, // User has read-only permissions + }, + 'events': { + 'm.room.name': 100, + 'm.room.power_levels': 100, + 'm.room.history_visibility': 100, + 'm.room.canonical_alias': 50, + 'm.room.avatar': 50, + 'm.room.tombstone': 100, + 'm.room.server_acl': 100, + 'm.room.encryption': 100, + 'm.space.child': 50, + 'm.room.topic': 50, + 'm.room.pinned_events': 50, + 'm.reaction': 0, + 'm.room.redaction': 0, + 'org.matrix.msc3401.call': 50, + 'org.matrix.msc3401.call.member': 50, + 'im.vector.modular.widgets': 50, + 'io.element.voice_broadcast_info': 50, + }, + 'events_default': 50, + 'invite': 50, + 'kick': 50, + 'notifications': {'room': 20}, + 'redact': 50, + 'state_default': 50, + 'users_default': 0, + }, + }; + + final response = await _matrixClient.post( + '/_matrix/client/v3/createRoom', + jsonEncode(roomData), + ); + + if (response.statusCode != 200) { + throw Exception( + 'Failed to create room: ${response.statusCode} - ${response.body}', + ); + } + + final responseData = jsonDecode(response.body) as Map; + final roomId = responseData['room_id'] as String; + + _log.info('Room created successfully: $roomId'); + + await _ensureRoomIsMarkedAsDirectChat(roomId, targetUserId); + return roomId; + } catch (e, stackTrace) { + _log.severe('Failed to create direct message room', e, stackTrace); + rethrow; + } + } + + /// Finds an existing direct message room with a specific user + Future _findExistingDirectMessageRoom({ + required String targetUserId, + required String currentUserId, + }) async { + try { + _log.info('Checking for existing direct message room with $targetUserId'); + + final existingRoomId = await _checkDirectRoomsInAccountData( + targetUserId: targetUserId, + currentUserId: currentUserId, + ); + + if (existingRoomId != null) { + _log.info('Found existing room: $existingRoomId'); + // Test if there is still access to the room + + return existingRoomId; + } + + _log.info('No existing direct message room found'); + return null; + } catch (e, stackTrace) { + _log.warning('Error finding existing room', e, stackTrace); + return null; + } + } + + /// Checks m.direct account data for existing rooms and validates them + Future _checkDirectRoomsInAccountData({ + required String targetUserId, + required String currentUserId, + }) async { + try { + final encodedUserId = Uri.encodeComponent(currentUserId); + final response = await _matrixClient.get( + '/_matrix/client/v3/user/$encodedUserId/account_data/m.direct', + ); + + if (response.statusCode != 200) { + _log.warning( + 'Failed to get m.direct account data: ${response.statusCode}', + ); + return null; + } + + final directRooms = jsonDecode(response.body) as Map; + final roomIds = directRooms[targetUserId] as List?; + + if (roomIds == null || roomIds.isEmpty) { + _log.info('No direct rooms found for $targetUserId'); + return null; + } + + _log.info('Found ${roomIds.length} potential rooms for $targetUserId'); + + for (final roomId in roomIds) { + final existingRoomId = roomId as String; + if (await _validateRoom(existingRoomId, currentUserId, targetUserId)) { + return existingRoomId; + } + } + + _log.info('No valid rooms found among ${roomIds.length} existing rooms'); + return null; + } catch (e, stackTrace) { + _log.warning( + 'Error checking direct rooms in account data', e, stackTrace); + return null; + } + } + + /// Validates that a room is accessible and has the correct members + Future _validateRoom( + String roomId, + String currentUserId, + String targetUserId, + ) async { + try { + // Check if current user is a member of the room + final encodedRoomId = MatrixHelper.encodeRoomId(roomId); + final encodedCurrentUserId = Uri.encodeComponent(currentUserId); + final currentUserResponse = await _matrixClient.get( + '/_matrix/client/v3/rooms/$encodedRoomId/state/m.room.member/$encodedCurrentUserId', + ); + + if (currentUserResponse.statusCode != 200) { + _log.info( + 'Cannot access room $roomId (status: ${currentUserResponse.statusCode})', + ); + await _leaveAndRemoveRoom(roomId, targetUserId, currentUserId); + return false; + } + + final currentMember = + jsonDecode(currentUserResponse.body) as Map; + if (currentMember['membership'] != 'join') { + _log.info('Current user is not a member of room $roomId'); + await _leaveAndRemoveRoom(roomId, targetUserId, currentUserId); + return false; + } + + // Check if target user is also a member of the room + final encodedTargetUserId = Uri.encodeComponent(targetUserId); + final targetUserResponse = await _matrixClient.get( + '/_matrix/client/v3/rooms/$encodedRoomId/state/m.room.member/$encodedTargetUserId', + ); + + if (targetUserResponse.statusCode != 200) { + _log.info( + 'Target user membership not found for room $roomId (status: ${targetUserResponse.statusCode})', + ); + await _leaveAndRemoveRoom(roomId, targetUserId, currentUserId); + return false; + } + + final targetMember = + jsonDecode(targetUserResponse.body) as Map; + if (targetMember['membership'] != 'join') { + _log.info('Target user is not a member of room $roomId'); + await _leaveAndRemoveRoom(roomId, targetUserId, currentUserId); + return false; + } + + _log.info('Valid room found: $roomId (both users are members)'); + return true; + } catch (e) { + _log.warning('Error validating room $roomId: $e'); + return false; + } + } + + /// Leaves a room and removes it from m.direct account data + Future _leaveAndRemoveRoom( + String roomId, + String targetUserId, + String currentUserId, + ) async { + try { + final encodedRoomId = MatrixHelper.encodeRoomId(roomId); + await _matrixClient.post( + '/_matrix/client/v3/rooms/$encodedRoomId/leave', + '{}', + ); + _log.info('Left room $roomId'); + + final encodedUserId = Uri.encodeComponent(currentUserId); + final response = await _matrixClient.get( + '/_matrix/client/v3/user/$encodedUserId/account_data/m.direct', + ); + + if (response.statusCode == 200) { + final directRooms = jsonDecode(response.body) as Map; + final roomIds = (directRooms[targetUserId] as List?) ?? []; + + directRooms[targetUserId] = + roomIds.where((room) => room != roomId).toList(); + + await _matrixClient.put( + '/_matrix/client/v3/user/$encodedUserId/account_data/m.direct', + jsonEncode(directRooms), + ); + _log.info('Removed room $roomId from m.direct'); + } + } catch (e) { + _log.warning('Error leaving/removing room $roomId: $e'); + } + } + + /// Ensures a room is marked as a direct chat in m.direct account data + Future _ensureRoomIsMarkedAsDirectChat( + String roomId, + String targetUserId, + ) async { + try { + final encodedUserId = Uri.encodeComponent(_currentUserId); + final response = await _matrixClient.get( + '/_matrix/client/v3/user/$encodedUserId/account_data/m.direct', + ); + + if (response.statusCode != 200) { + _log.warning( + 'Failed to get m.direct data: ${response.statusCode}', + ); + return; + } + + final directRooms = jsonDecode(response.body) as Map; + final roomIds = (directRooms[targetUserId] as List?) ?? []; + + if (roomIds.contains(roomId)) { + _log.info('Room $roomId already marked as direct chat'); + return; + } + + _log.info('Marking room $roomId as direct chat with $targetUserId'); + directRooms[targetUserId] = [...roomIds, roomId]; + + await _matrixClient.put( + '/_matrix/client/v3/user/$encodedUserId/account_data/m.direct', + jsonEncode(directRooms), + ); + _log.info('Room marked as direct chat successfully'); + } catch (e) { + _log.warning('Error ensuring room is marked as direct chat: $e'); + } + } + + /// Clean up resources + void dispose() { + _matrixClient.dispose(); + } +} diff --git a/school_data_hub_server/lib/src/utils/matrix_notifications/schoolday_event_notification_text.dart b/school_data_hub_server/lib/src/utils/matrix_notifications/schoolday_event_notification_text.dart new file mode 100644 index 00000000..3fe6769e --- /dev/null +++ b/school_data_hub_server/lib/src/utils/matrix_notifications/schoolday_event_notification_text.dart @@ -0,0 +1,96 @@ +import 'package:school_data_hub_server/src/generated/protocol.dart'; + +/// Returns plain text notification message +String getSchooldayEventNotificationText( + {required String eventcreator, + required String pupilName, + required String dateTimeAsString, + required SchooldayEvent schooldayEvent, + bool? processedStatusChange, + int? numberOfEvents}) { + final String eventType = switch (schooldayEvent.eventType) { + SchooldayEventType.admonition => 'Rote Karte 🚫', + SchooldayEventType.admonitionAndBanned => 'Rote Karte und Abholen 🚫🏠️', + SchooldayEventType.afternoonCareAdmonition => 'Rote Karte OGS ⚠️🍽️', + SchooldayEventType.parentsMeeting => 'Elterngespräch 👪💬', + SchooldayEventType.otherEvent => 'Sonstiges 🗒️', + // TODO: Handle this case. + SchooldayEventType.notSet => '❓️', + }; + final String eventReason = schooldayEvent.eventReason + .replaceFirst('gm', '🤜🤕') + .replaceFirst('gl', '🤜🎓️') + .replaceFirst('gs', '🤜🏫') + .replaceFirst('ab', '🤬💔') + .replaceFirst('gv', '🚨😱') + .replaceFirst('äa', '😈😖') + .replaceFirst('il', '🎓️🙉') + .replaceFirst('us', '🛑🎓️') + .replaceFirst('ss', '📝') + .replaceFirst('le', '💡🧠') + .replaceFirst('fi', '🛟🧠') + .replaceFirst('ki', '⚠️ℹ️'); + + return ''' +$eventType +für $pupilName +$eventReason + +von $eventcreator am $dateTimeAsString + +${numberOfEvents != null ? 'Das ist die $numberOfEvents. Schulereignis für $pupilName.' : ''} +'''; +} + +/// Returns HTML formatted notification message for Matrix +String getSchooldayEventNotificationHtml({ + required String eventcreator, + required String pupilName, + required String dateTimeAsString, + required SchooldayEvent schooldayEvent, + int? numberOfEvents, + bool? processedStatusChange, +}) { + final String eventType = switch (schooldayEvent.eventType) { + SchooldayEventType.admonition => 'Rote Karte 🚫', + SchooldayEventType.admonitionAndBanned => 'Rote Karte und Abholen 🚫🏠️', + SchooldayEventType.afternoonCareAdmonition => 'Rote Karte OGS ⚠️🍽️', + SchooldayEventType.parentsMeeting => 'Elterngespräch 👪💬', + SchooldayEventType.otherEvent => 'Sonstiges 🗒️', + SchooldayEventType.notSet => '❓️', + }; + + final String eventReason = schooldayEvent.eventReason + .replaceFirst('gm', '🤜🤕 Gewalt gegen Kinder') + .replaceFirst('gl', '🤜🎓️ Gewalt gegen Erwachsene') + .replaceFirst('gs', '🤜🏫 Gewalt gegen Sachen') + .replaceFirst('ab', '🤬💔 Beleidigen') + .replaceFirst('gv', '🚨😱 Gefahr für sich/andere') + .replaceFirst('äa', '😈😖 Ärgern') + .replaceFirst('il', '🎓️🙉 Anweisungen ignorieren') + .replaceFirst('us', '🛑🎓️ Unterricht stören') + .replaceFirst('ss', '📝 Sonstiges') + .replaceFirst('le', '💡🧠 Lernentwicklung') + .replaceFirst('fi', '🛟🧠 Förderung') + .replaceFirst('ki', '⚠️ℹ️ Regelverstoß'); + + // Escape HTML entities + String escapeHtml(String text) { + return text + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); + } + + return ''' +

${escapeHtml(eventType)}

für

+

${escapeHtml(pupilName)}

+

Grund:

+

${escapeHtml(eventReason).replaceAll('*', '
')}

+ ${processedStatusChange != null ? schooldayEvent.processed == true ? '

Status: Bearbeitet von ${escapeHtml(eventcreator)} am ${escapeHtml(dateTimeAsString)}' : '

Status: Nicht bearbeitet' : '

Eingetragen von ${escapeHtml(eventcreator)} am ${escapeHtml(dateTimeAsString)}

'} + +${numberOfEvents != null ? '

Das ist das $numberOfEvents. Schulereignis dieser Art für ${escapeHtml(pupilName)}.

' : ''} +'''; +} diff --git a/school_data_hub_server/migrations/20251121184735230/definition.json b/school_data_hub_server/migrations/20251121184735230/definition.json new file mode 100644 index 00000000..0fa6b590 --- /dev/null +++ b/school_data_hub_server/migrations/20251121184735230/definition.json @@ -0,0 +1,6520 @@ +{ + "moduleName": "school_data_hub", + "tables": [ + { + "name": "authorization", + "dartName": "Authorization", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('authorization_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "authorization_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "book", + "dartName": "Book", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "title", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "author", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "readingLevel", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "imagePath", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "book_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "book_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "isbn" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "book_tag", + "dartName": "BookTag", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_tag_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "book_tag_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "book_tagging", + "dartName": "BookTagging", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_tagging_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "bookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "bookTagId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "book_tagging_fk_0", + "columns": [ + "bookId" + ], + "referenceTable": "book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "book_tagging_fk_1", + "columns": [ + "bookTagId" + ], + "referenceTable": "book_tag", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "book_tagging_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "book_tagging_index_idx", + "elements": [ + { + "type": 0, + "definition": "bookId" + }, + { + "type": 0, + "definition": "bookTagId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "competence", + "dartName": "Competence", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentCompetence", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "level", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "indicators", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "competence_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_check", + "dartName": "CompetenceCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "checkId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "valueFactor", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "groupCheckId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "groupCheckName", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_check_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_check_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_goal", + "dartName": "CompetenceGoal", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_goal_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "strategies", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "score", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_goal_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_goal_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_goal_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report", + "dartName": "CompetenceReport", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "reportId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievement", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_report_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_fk_1", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_report_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report_check", + "dartName": "CompetenceReportCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievement", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceReportId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_report_check_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_check_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence_report_item", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_check_fk_2", + "columns": [ + "competenceReportId" + ], + "referenceTable": "competence_report", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_report_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report_item", + "dartName": "CompetenceReportItem", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_item_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentItem", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "level", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "competence_report_item_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "compulsory_room", + "dartName": "CompulsoryRoom", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('compulsory_room_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "roomId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:MatrixRoomType" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "compulsory_room_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "credit_transaction", + "dartName": "CreditTransaction", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('credit_transaction_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "sender", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "receiver", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "amount", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "dateTime", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "description", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "_pupilDataCredittransactionsPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "credit_transaction_fk_0", + "columns": [ + "_pupilDataCredittransactionsPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "credit_transaction_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "reciever_idx", + "elements": [ + { + "type": 0, + "definition": "receiver" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "sender_idx", + "elements": [ + { + "type": 0, + "definition": "sender" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "hub_document", + "dartName": "HubDocument", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('hub_document_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "documentId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "documentPath", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_competenceCheckDocumentsCompetenceCheckId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_competenceGoalDocumentsCompetenceGoalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryStatusDocumentsSupportCategoryStatusId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportGoalCheckDocumentsSupportGoalCheckId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_preSchoolTestPreschooltestdocumentsPreSchoolTestId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "hub_document_fk_0", + "columns": [ + "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId" + ], + "referenceTable": "pupil_book_lending", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_1", + "columns": [ + "_competenceCheckDocumentsCompetenceCheckId" + ], + "referenceTable": "competence_check", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_2", + "columns": [ + "_competenceGoalDocumentsCompetenceGoalId" + ], + "referenceTable": "competence_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_3", + "columns": [ + "_supportCategoryStatusDocumentsSupportCategoryStatusId" + ], + "referenceTable": "support_category_status", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_4", + "columns": [ + "_supportGoalCheckDocumentsSupportGoalCheckId" + ], + "referenceTable": "support_goal_check", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_5", + "columns": [ + "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId" + ], + "referenceTable": "pre_school_medical", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_6", + "columns": [ + "_preSchoolTestPreschooltestdocumentsPreSchoolTestId" + ], + "referenceTable": "pre_school_test", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "hub_document_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "kindergarden", + "dartName": "Kindergarden", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('kindergarden_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "phone", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "address", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "contactPerson", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "kindergarden_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "last_pupil_identities_update", + "dartName": "LastPupilIdentiesUpdate", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('last_pupil_identities_update_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "date", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "last_pupil_identities_update_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "learning_support_plan", + "dartName": "LearningSupportPlan", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('learning_support_plan_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "planId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "number", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "socialPedagogue", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "proffesionalsInvolved", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "strengthsDescription", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "problemsDescription", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "learningSupportLevelId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "learning_support_plan_fk_0", + "columns": [ + "learningSupportLevelId" + ], + "referenceTable": "support_level", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "learning_support_plan_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "learning_support_plan_fk_2", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "learning_support_plan_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson", + "dartName": "Lesson", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "subjectId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_fk_0", + "columns": [ + "subjectId" + ], + "referenceTable": "subject", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_attendance", + "dartName": "LessonAttendance", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_attendance_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_attendance_fk_0", + "columns": [ + "lessonId" + ], + "referenceTable": "lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_attendance_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_attendance_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_group", + "dartName": "LessonGroup", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_group_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "color", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_group_fk_0", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_group_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_group_pupil", + "dartName": "ScheduledLessonGroupMembership", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_group_pupil_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lessonGroupId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilDataId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_group_pupil_fk_0", + "columns": [ + "lessonGroupId" + ], + "referenceTable": "lesson_group", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_group_pupil_fk_1", + "columns": [ + "pupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_group_pupil_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "lesson_group_membership_index_idx", + "elements": [ + { + "type": 0, + "definition": "lessonGroupId" + }, + { + "type": 0, + "definition": "pupilDataId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "lesson_teacher", + "dartName": "LessonTeacher", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_teacher_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledLessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_teacher_fk_0", + "columns": [ + "userId" + ], + "referenceTable": "user", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_teacher_fk_1", + "columns": [ + "scheduledLessonId" + ], + "referenceTable": "lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_teacher_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "lesson_teacher_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "scheduledLessonId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "library_book", + "dartName": "LibraryBook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('library_book_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "libraryId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "bookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "locationId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "available", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [ + { + "constraintName": "library_book_fk_0", + "columns": [ + "bookId" + ], + "referenceTable": "book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "library_book_fk_1", + "columns": [ + "locationId" + ], + "referenceTable": "library_book_location", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "library_book_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "library_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "libraryId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "library_book_location", + "dartName": "LibraryBookLocation", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('library_book_location_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "location", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "library_book_location_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "location_unique_idx", + "elements": [ + { + "type": 0, + "definition": "location" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "missed_class", + "dartName": "MissedSchoolday", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('missed_class_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "missedType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:MissedType" + }, + { + "name": "unexcused", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "contacted", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:ContactedType" + }, + { + "name": "returned", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "returnedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "writtenExcuse", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "minutesLate", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "schooldayId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "missed_class_fk_0", + "columns": [ + "schooldayId" + ], + "referenceTable": "schoolday", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "missed_class_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "missed_class_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "schoolday_pupil_data_idx", + "elements": [ + { + "type": 0, + "definition": "schooldayId" + }, + { + "type": 0, + "definition": "pupilId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "pre_school_medical", + "dartName": "PreSchoolMedical", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pre_school_medical_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "preschoolMedicalStatus", + "columnType": 0, + "isNullable": true, + "dartType": "protocol:PreSchoolMedicalStatus?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "updatedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "updatedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "pre_school_medical_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pre_school_test", + "dartName": "PreSchoolTest", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pre_school_test_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "careNeedsIntensity", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "pre_school_test_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_authorization", + "dartName": "PupilAuthorization", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_authorization_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "fileId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "authorizationId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_authorization_fk_0", + "columns": [ + "fileId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_authorization_fk_1", + "columns": [ + "authorizationId" + ], + "referenceTable": "authorization", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_authorization_fk_2", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_authorization_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_book_lending", + "dartName": "PupilBookLending", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_book_lending_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lendingId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "status", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lentAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "lentBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "returnedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "receivedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "libraryBookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_book_lending_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_book_lending_fk_1", + "columns": [ + "libraryBookId" + ], + "referenceTable": "library_book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_book_lending_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_data", + "dartName": "PupilData", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_data_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:PupilStatus" + }, + { + "name": "internalId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "password", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "preSchoolMedicalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "kindergardenId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "kindergardenData", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:KindergardenInfo?" + }, + { + "name": "preSchoolTestId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "avatarId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "avatarAuthId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "publicMediaAuth", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:PublicMediaAuth" + }, + { + "name": "publicMediaAuthDocumentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "contact", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "communicationPupil", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:CommunicationSkills?" + }, + { + "name": "specialInformation", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "tutorInfo", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:TutorInfo?" + }, + { + "name": "afterSchoolCare", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:AfterSchoolCare?" + }, + { + "name": "credit", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "creditEarned", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolyearHeldBackAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "swimmer", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "_kindergardenPupilsKindergardenId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_data_fk_0", + "columns": [ + "preSchoolMedicalId" + ], + "referenceTable": "pre_school_medical", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_1", + "columns": [ + "kindergardenId" + ], + "referenceTable": "kindergarden", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_2", + "columns": [ + "preSchoolTestId" + ], + "referenceTable": "pre_school_test", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_3", + "columns": [ + "avatarId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_4", + "columns": [ + "avatarAuthId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_5", + "columns": [ + "publicMediaAuthDocumentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_6", + "columns": [ + "_kindergardenPupilsKindergardenId" + ], + "referenceTable": "kindergarden", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_data_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "pupil_data_status_idx", + "elements": [ + { + "type": 0, + "definition": "status" + }, + { + "type": 0, + "definition": "internalId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "pupil_data_internal_id_idx", + "elements": [ + { + "type": 0, + "definition": "internalId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "pupil_list_entry", + "dartName": "PupilListEntry", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_list_entry_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "entryBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "schoolListId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_list_entry_fk_0", + "columns": [ + "schoolListId" + ], + "referenceTable": "school_list", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_list_entry_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "pupil_list_entry_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_workbook", + "dartName": "PupilWorkbook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_workbook_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "finishedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "workbookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_workbook_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_workbook_fk_1", + "columns": [ + "workbookId" + ], + "referenceTable": "workbook", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "pupil_workbook_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "room", + "dartName": "Classroom", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('room_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "roomCode", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "room_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "scheduled_lesson", + "dartName": "ScheduledLesson", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('scheduled_lesson_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "active", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "subjectId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledAtId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "timetableSlotOrder", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "mainTeacherId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lessonId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lessonGroupId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "recordtest", + "columnType": 8, + "isNullable": true, + "dartType": "( {int testint, String testString})?" + }, + { + "name": "_roomScheduledlessonsRoomId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "scheduled_lesson_fk_0", + "columns": [ + "subjectId" + ], + "referenceTable": "subject", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_1", + "columns": [ + "scheduledAtId" + ], + "referenceTable": "timetable_slot", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_2", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_3", + "columns": [ + "roomId" + ], + "referenceTable": "room", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_4", + "columns": [ + "lessonGroupId" + ], + "referenceTable": "lesson_group", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_5", + "columns": [ + "_roomScheduledlessonsRoomId" + ], + "referenceTable": "room", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "scheduled_lesson_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "scheduled_lesson_teacher", + "dartName": "ScheduledLessonTeacher", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('scheduled_lesson_teacher_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledLessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "scheduled_lesson_teacher_fk_0", + "columns": [ + "userId" + ], + "referenceTable": "user", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_teacher_fk_1", + "columns": [ + "scheduledLessonId" + ], + "referenceTable": "scheduled_lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "scheduled_lesson_teacher_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "scheduled_lesson_teacher_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "scheduledLessonId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "school_data", + "dartName": "SchoolData", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_data_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "officialName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "address", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "schoolNumber", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "telephoneNumber", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "website", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "logoId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "officialSealId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "school_data_fk_0", + "columns": [ + "logoId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "school_data_fk_1", + "columns": [ + "officialSealId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "school_data_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "school_list", + "dartName": "SchoolList", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_list_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "listId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "archived", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "public", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "authorizedUsers", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "school_list_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "school_semester", + "dartName": "SchoolSemester", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_semester_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "schoolYear", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "isFirst", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "startDate", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "endDate", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "classConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "supportConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "reportConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "reportSignedDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "school_semester_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "schoolday", + "dartName": "Schoolday", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('schoolday_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "schoolday", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "schoolday_fk_0", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "schoolday_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "schoolday_event", + "dartName": "SchooldayEvent", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('schoolday_event_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "eventId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "eventType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:SchooldayEventType" + }, + { + "name": "eventReason", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "processed", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "processedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "processedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "documentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "processedDocumentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "schooldayId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "schoolday_event_fk_0", + "columns": [ + "documentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_1", + "columns": [ + "processedDocumentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_2", + "columns": [ + "schooldayId" + ], + "referenceTable": "schoolday", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_3", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "schoolday_event_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "subject", + "dartName": "Subject", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('subject_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "color", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "subject_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category", + "dartName": "SupportCategory", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "categoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentCategory", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "support_category_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category_goal", + "dartName": "SupportGoal", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_goal_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "goalId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "strategies", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "supportCategoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_learningSupportPlanSupportgoalsLearningSupportPlanId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryCategorygoalsSupportCategoryId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_pupilDataSupportgoalsPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_category_goal_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_1", + "columns": [ + "supportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "support_category_goal_fk_2", + "columns": [ + "_learningSupportPlanSupportgoalsLearningSupportPlanId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_3", + "columns": [ + "_supportCategoryCategorygoalsSupportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_4", + "columns": [ + "_pupilDataSupportgoalsPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_category_goal_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category_status", + "dartName": "SupportCategoryStatus", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_status_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "supportCategoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "learningSupportPlanId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryCategorystatuesSupportCategoryId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_pupilDataSupportcategorystatusesPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_category_status_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_1", + "columns": [ + "supportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_2", + "columns": [ + "learningSupportPlanId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_3", + "columns": [ + "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_4", + "columns": [ + "_supportCategoryCategorystatuesSupportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_5", + "columns": [ + "_pupilDataSupportcategorystatusesPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_category_status_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_goal_check", + "dartName": "SupportGoalCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_goal_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "checkId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "supportGoalId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_supportCategoryGoalGoalchecksSupportCategoryGoalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_goal_check_fk_0", + "columns": [ + "supportGoalId" + ], + "referenceTable": "support_category_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "support_goal_check_fk_1", + "columns": [ + "_supportCategoryGoalGoalchecksSupportCategoryGoalId" + ], + "referenceTable": "support_category_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_goal_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_level", + "dartName": "SupportLevel", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_level_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "level", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "support_level_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "support_level_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "timetable", + "dartName": "Timetable", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('timetable_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "active", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "startsAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "endsAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modified", + "columnType": 8, + "isNullable": true, + "dartType": "List<( {String modifiedBy, DateTime modifiedAt})>?" + } + ], + "foreignKeys": [ + { + "constraintName": "timetable_fk_0", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "timetable_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "timetable_school_semester_idx", + "elements": [ + { + "type": 0, + "definition": "schoolSemesterId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "timetable_slot", + "dartName": "TimetableSlot", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('timetable_slot_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "day", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:Weekday" + }, + { + "name": "startTime", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "endTime", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "timetable_slot_fk_0", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "timetable_slot_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "user", + "dartName": "User", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('user_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userInfoId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "role", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:Role" + }, + { + "name": "matrixUserId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "timeUnits", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "reliefTimeUnits", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilsAuth", + "columnType": 8, + "isNullable": true, + "dartType": "Set?" + }, + { + "name": "credit", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "userFlags", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:UserFlags" + } + ], + "foreignKeys": [ + { + "constraintName": "user_fk_0", + "columns": [ + "userInfoId" + ], + "referenceTable": "serverpod_user_info", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "user_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "user_info_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userInfoId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "user_device", + "dartName": "UserDevice", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('user_device_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userInfoId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "deviceId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "deviceName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "lastLogin", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "isActive", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "authId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "user_device_fk_0", + "columns": [ + "userInfoId" + ], + "referenceTable": "serverpod_user_info", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "user_device_fk_1", + "columns": [ + "authId" + ], + "referenceTable": "serverpod_auth_key", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "user_device_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "auth_key_user_device_idx", + "elements": [ + { + "type": 0, + "definition": "authId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "workbook", + "dartName": "Workbook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('workbook_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "subject", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "level", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "amount", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "imageUrl", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "workbook_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "serverpod_cloud_storage", + "dartName": "CloudStorageEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_cloud_storage_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "storageId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "path", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "addedTime", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "expiration", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "byteData", + "columnType": 5, + "isNullable": false, + "dartType": "dart:typed_data:ByteData" + }, + { + "name": "verified", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_cloud_storage_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_cloud_storage_path_idx", + "elements": [ + { + "type": 0, + "definition": "storageId" + }, + { + "type": 0, + "definition": "path" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + }, + { + "indexName": "serverpod_cloud_storage_expiration", + "elements": [ + { + "type": 0, + "definition": "expiration" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_cloud_storage_direct_upload", + "dartName": "CloudStorageDirectUploadEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_cloud_storage_direct_upload_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "storageId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "path", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "expiration", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "authKey", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_cloud_storage_direct_upload_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_cloud_storage_direct_upload_storage_path", + "elements": [ + { + "type": 0, + "definition": "storageId" + }, + { + "type": 0, + "definition": "path" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_future_call", + "dartName": "FutureCallEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_future_call_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "time", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "serializedObject", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "identifier", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_future_call_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_future_call_time_idx", + "elements": [ + { + "type": 0, + "definition": "time" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_future_call_serverId_idx", + "elements": [ + { + "type": 0, + "definition": "serverId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_future_call_identifier_idx", + "elements": [ + { + "type": 0, + "definition": "identifier" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_health_connection_info", + "dartName": "ServerHealthConnectionInfo", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_health_connection_info_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timestamp", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "active", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "closing", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "idle", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "granularity", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_health_connection_info_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_health_connection_info_timestamp_idx", + "elements": [ + { + "type": 0, + "definition": "timestamp" + }, + { + "type": 0, + "definition": "serverId" + }, + { + "type": 0, + "definition": "granularity" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_health_metric", + "dartName": "ServerHealthMetric", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_health_metric_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timestamp", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "isHealthy", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "value", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "granularity", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_health_metric_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_health_metric_timestamp_idx", + "elements": [ + { + "type": 0, + "definition": "timestamp" + }, + { + "type": 0, + "definition": "serverId" + }, + { + "type": 0, + "definition": "name" + }, + { + "type": 0, + "definition": "granularity" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_log", + "dartName": "LogEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_log_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "sessionLogId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "messageId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "reference", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "time", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "logLevel", + "columnType": 6, + "isNullable": false, + "dartType": "protocol:LogLevel" + }, + { + "name": "message", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "error", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "stackTrace", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "serverpod_log_fk_0", + "columns": [ + "sessionLogId" + ], + "referenceTable": "serverpod_session_log", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "serverpod_log_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_log_sessionLogId_idx", + "elements": [ + { + "type": 0, + "definition": "sessionLogId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_message_log", + "dartName": "MessageLogEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_message_log_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "sessionLogId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "messageId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "endpoint", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "messageName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "duration", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "error", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "stackTrace", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "slow", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "order", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "serverpod_message_log_fk_0", + "columns": [ + "sessionLogId" + ], + "referenceTable": "serverpod_session_log", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "serverpod_message_log_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "serverpod_method", + "dartName": "MethodInfo", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_method_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "endpoint", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "method", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_method_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_method_endpoint_method_idx", + "elements": [ + { + "type": 0, + "definition": "endpoint" + }, + { + "type": 0, + "definition": "method" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_migrations", + "dartName": "DatabaseMigrationVersion", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_migrations_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "module", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "version", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timestamp", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_migrations_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_migrations_ids", + "elements": [ + { + "type": 0, + "definition": "module" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_query_log", + "dartName": "QueryLogEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_query_log_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "sessionLogId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "messageId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "query", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "duration", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "numRows", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "error", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "stackTrace", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "slow", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "order", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "serverpod_query_log_fk_0", + "columns": [ + "sessionLogId" + ], + "referenceTable": "serverpod_session_log", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "serverpod_query_log_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_query_log_sessionLogId_idx", + "elements": [ + { + "type": 0, + "definition": "sessionLogId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_readwrite_test", + "dartName": "ReadWriteTestEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_readwrite_test_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "number", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_readwrite_test_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "serverpod_runtime_settings", + "dartName": "RuntimeSettings", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_runtime_settings_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "logSettings", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:LogSettings" + }, + { + "name": "logSettingsOverrides", + "columnType": 8, + "isNullable": false, + "dartType": "List" + }, + { + "name": "logServiceCalls", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "logMalformedCalls", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_runtime_settings_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "serverpod_session_log", + "dartName": "SessionLogEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_session_log_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "time", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "module", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "endpoint", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "method", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "duration", + "columnType": 3, + "isNullable": true, + "dartType": "double?" + }, + { + "name": "numQueries", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "slow", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "error", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "stackTrace", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "authenticatedUserId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "isOpen", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "touched", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_session_log_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_session_log_serverid_idx", + "elements": [ + { + "type": 0, + "definition": "serverId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_session_log_touched_idx", + "elements": [ + { + "type": 0, + "definition": "touched" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_session_log_isopen_idx", + "elements": [ + { + "type": 0, + "definition": "isOpen" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_auth_key", + "dartName": "AuthKey", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_auth_key_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "hash", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "scopeNames", + "columnType": 8, + "isNullable": false, + "dartType": "List" + }, + { + "name": "method", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_auth_key_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_auth_key_userId_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_email_auth", + "dartName": "EmailAuth", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_email_auth_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "hash", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_email_auth_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_email_auth_email", + "elements": [ + { + "type": 0, + "definition": "email" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_email_create_request", + "dartName": "EmailCreateAccountRequest", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_email_create_request_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "hash", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "verificationCode", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_email_create_request_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_email_auth_create_account_request_idx", + "elements": [ + { + "type": 0, + "definition": "email" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_email_failed_sign_in", + "dartName": "EmailFailedSignIn", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_email_failed_sign_in_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "time", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "ipAddress", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_email_failed_sign_in_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_email_failed_sign_in_email_idx", + "elements": [ + { + "type": 0, + "definition": "email" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_email_failed_sign_in_time_idx", + "elements": [ + { + "type": 0, + "definition": "time" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_email_reset", + "dartName": "EmailReset", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_email_reset_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "verificationCode", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "expiration", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_email_reset_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_email_reset_verification_idx", + "elements": [ + { + "type": 0, + "definition": "verificationCode" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_google_refresh_token", + "dartName": "GoogleRefreshToken", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_google_refresh_token_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "refreshToken", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_google_refresh_token_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_google_refresh_token_userId_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_user_image", + "dartName": "UserImage", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_user_image_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "version", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "url", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_user_image_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_user_image_user_id", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "version" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_user_info", + "dartName": "UserInfo", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_user_info_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userIdentifier", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "userName", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "fullName", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "email", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "created", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "imageUrl", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "scopeNames", + "columnType": 8, + "isNullable": false, + "dartType": "List" + }, + { + "name": "blocked", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_user_info_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_user_info_user_identifier", + "elements": [ + { + "type": 0, + "definition": "userIdentifier" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + }, + { + "indexName": "serverpod_user_info_email", + "elements": [ + { + "type": 0, + "definition": "email" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + } + ], + "installedModules": [ + { + "module": "school_data_hub", + "version": "20251121184735230" + }, + { + "module": "serverpod", + "version": "20240516151843329" + }, + { + "module": "serverpod_auth", + "version": "20240520102713718" + } + ], + "migrationApiVersion": 1 +} \ No newline at end of file diff --git a/school_data_hub_server/migrations/20251121184735230/definition.sql b/school_data_hub_server/migrations/20251121184735230/definition.sql new file mode 100644 index 00000000..ce0eb8f0 --- /dev/null +++ b/school_data_hub_server/migrations/20251121184735230/definition.sql @@ -0,0 +1,1726 @@ +BEGIN; + +-- +-- Class Authorization as table authorization +-- +CREATE TABLE "authorization" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "description" text NOT NULL, + "createdBy" text NOT NULL +); + +-- +-- Class Book as table book +-- +CREATE TABLE "book" ( + "id" bigserial PRIMARY KEY, + "isbn" bigint NOT NULL, + "title" text NOT NULL, + "author" text NOT NULL, + "description" text NOT NULL, + "readingLevel" text, + "imagePath" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "book_id_unique_idx" ON "book" USING btree ("isbn"); + +-- +-- Class BookTag as table book_tag +-- +CREATE TABLE "book_tag" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL +); + +-- +-- Class BookTagging as table book_tagging +-- +CREATE TABLE "book_tagging" ( + "id" bigserial PRIMARY KEY, + "bookId" bigint NOT NULL, + "bookTagId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "book_tagging_index_idx" ON "book_tagging" USING btree ("bookId", "bookTagId"); + +-- +-- Class Competence as table competence +-- +CREATE TABLE "competence" ( + "id" bigserial PRIMARY KEY, + "publicId" bigint NOT NULL, + "parentCompetence" bigint, + "name" text NOT NULL, + "level" json, + "indicators" json, + "order" bigint +); + +-- +-- Class CompetenceCheck as table competence_check +-- +CREATE TABLE "competence_check" ( + "id" bigserial PRIMARY KEY, + "checkId" text NOT NULL, + "score" bigint NOT NULL, + "comment" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "valueFactor" double precision NOT NULL, + "groupCheckId" text, + "groupCheckName" text, + "pupilId" bigint NOT NULL, + "competenceId" bigint NOT NULL +); + +-- +-- Class CompetenceGoal as table competence_goal +-- +CREATE TABLE "competence_goal" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "description" text NOT NULL, + "strategies" json, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text NOT NULL, + "score" bigint, + "achievedAt" timestamp without time zone NOT NULL, + "pupilId" bigint NOT NULL, + "competenceId" bigint NOT NULL +); + +-- +-- Class CompetenceReport as table competence_report +-- +CREATE TABLE "competence_report" ( + "id" bigserial PRIMARY KEY, + "reportId" text NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text NOT NULL, + "achievement" text NOT NULL, + "achievedAt" timestamp without time zone NOT NULL, + "pupilId" bigint NOT NULL, + "schoolSemesterId" bigint NOT NULL +); + +-- +-- Class CompetenceReportCheck as table competence_report_check +-- +CREATE TABLE "competence_report_check" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "achievement" bigint NOT NULL, + "comment" text NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "pupilId" bigint NOT NULL, + "competenceId" bigint NOT NULL, + "competenceReportId" bigint NOT NULL +); + +-- +-- Class CompetenceReportItem as table competence_report_item +-- +CREATE TABLE "competence_report_item" ( + "id" bigserial PRIMARY KEY, + "publicId" bigint NOT NULL, + "parentItem" bigint, + "name" text NOT NULL, + "level" json, + "order" bigint +); + +-- +-- Class CompulsoryRoom as table compulsory_room +-- +CREATE TABLE "compulsory_room" ( + "id" bigserial PRIMARY KEY, + "roomId" text NOT NULL, + "roomType" text NOT NULL +); + +-- +-- Class CreditTransaction as table credit_transaction +-- +CREATE TABLE "credit_transaction" ( + "id" bigserial PRIMARY KEY, + "sender" text NOT NULL, + "receiver" bigint NOT NULL, + "amount" bigint NOT NULL, + "dateTime" timestamp without time zone NOT NULL, + "description" text, + "_pupilDataCredittransactionsPupilDataId" bigint +); + +-- Indexes +CREATE INDEX "reciever_idx" ON "credit_transaction" USING btree ("receiver"); +CREATE INDEX "sender_idx" ON "credit_transaction" USING btree ("sender"); + +-- +-- Class HubDocument as table hub_document +-- +CREATE TABLE "hub_document" ( + "id" bigserial PRIMARY KEY, + "documentId" text NOT NULL, + "documentPath" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId" bigint, + "_competenceCheckDocumentsCompetenceCheckId" bigint, + "_competenceGoalDocumentsCompetenceGoalId" bigint, + "_supportCategoryStatusDocumentsSupportCategoryStatusId" bigint, + "_supportGoalCheckDocumentsSupportGoalCheckId" bigint, + "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId" bigint, + "_preSchoolTestPreschooltestdocumentsPreSchoolTestId" bigint +); + +-- +-- Class Kindergarden as table kindergarden +-- +CREATE TABLE "kindergarden" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "phone" text NOT NULL, + "address" text NOT NULL, + "email" text NOT NULL, + "contactPerson" text NOT NULL +); + +-- +-- Class LastPupilIdentiesUpdate as table last_pupil_identities_update +-- +CREATE TABLE "last_pupil_identities_update" ( + "id" bigserial PRIMARY KEY, + "date" timestamp without time zone +); + +-- +-- Class LearningSupportPlan as table learning_support_plan +-- +CREATE TABLE "learning_support_plan" ( + "id" bigserial PRIMARY KEY, + "planId" text NOT NULL, + "number" bigint, + "createdBy" text NOT NULL, + "socialPedagogue" text, + "proffesionalsInvolved" text, + "strengthsDescription" text, + "problemsDescription" text, + "learningSupportLevelId" bigint NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "comment" text, + "pupilId" bigint NOT NULL, + "schoolSemesterId" bigint NOT NULL +); + +-- +-- Class Lesson as table lesson +-- +CREATE TABLE "lesson" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "subjectId" bigint NOT NULL +); + +-- +-- Class LessonAttendance as table lesson_attendance +-- +CREATE TABLE "lesson_attendance" ( + "id" bigserial PRIMARY KEY, + "lessonId" bigint NOT NULL, + "pupilId" bigint NOT NULL, + "comment" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text NOT NULL, + "modifiedAt" timestamp without time zone NOT NULL +); + +-- +-- Class LessonGroup as table lesson_group +-- +CREATE TABLE "lesson_group" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "name" text NOT NULL, + "color" text, + "timetableId" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text, + "modifiedAt" timestamp without time zone +); + +-- +-- Class ScheduledLessonGroupMembership as table lesson_group_pupil +-- +CREATE TABLE "lesson_group_pupil" ( + "id" bigserial PRIMARY KEY, + "lessonGroupId" bigint NOT NULL, + "pupilDataId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "lesson_group_membership_index_idx" ON "lesson_group_pupil" USING btree ("lessonGroupId", "pupilDataId"); + +-- +-- Class LessonTeacher as table lesson_teacher +-- +CREATE TABLE "lesson_teacher" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "scheduledLessonId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "lesson_teacher_unique_idx" ON "lesson_teacher" USING btree ("userId", "scheduledLessonId"); + +-- +-- Class LibraryBook as table library_book +-- +CREATE TABLE "library_book" ( + "id" bigserial PRIMARY KEY, + "libraryId" text NOT NULL, + "bookId" bigint NOT NULL, + "locationId" bigint NOT NULL, + "available" boolean NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "library_id_unique_idx" ON "library_book" USING btree ("libraryId"); + +-- +-- Class LibraryBookLocation as table library_book_location +-- +CREATE TABLE "library_book_location" ( + "id" bigserial PRIMARY KEY, + "location" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "location_unique_idx" ON "library_book_location" USING btree ("location"); + +-- +-- Class MissedSchoolday as table missed_class +-- +CREATE TABLE "missed_class" ( + "id" bigserial PRIMARY KEY, + "missedType" text NOT NULL, + "unexcused" boolean NOT NULL, + "contacted" text NOT NULL, + "returned" boolean NOT NULL, + "returnedAt" timestamp without time zone, + "writtenExcuse" boolean NOT NULL, + "minutesLate" bigint, + "createdBy" text NOT NULL, + "modifiedBy" text, + "comment" text, + "schooldayId" bigint NOT NULL, + "pupilId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "schoolday_pupil_data_idx" ON "missed_class" USING btree ("schooldayId", "pupilId"); + +-- +-- Class PreSchoolMedical as table pre_school_medical +-- +CREATE TABLE "pre_school_medical" ( + "id" bigserial PRIMARY KEY, + "preschoolMedicalStatus" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "updatedBy" text, + "updatedAt" timestamp without time zone +); + +-- +-- Class PreSchoolTest as table pre_school_test +-- +CREATE TABLE "pre_school_test" ( + "id" bigserial PRIMARY KEY, + "careNeedsIntensity" bigint +); + +-- +-- Class PupilAuthorization as table pupil_authorization +-- +CREATE TABLE "pupil_authorization" ( + "id" bigserial PRIMARY KEY, + "status" boolean, + "comment" text, + "createdBy" text, + "fileId" bigint, + "authorizationId" bigint NOT NULL, + "pupilId" bigint NOT NULL +); + +-- +-- Class PupilBookLending as table pupil_book_lending +-- +CREATE TABLE "pupil_book_lending" ( + "id" bigserial PRIMARY KEY, + "lendingId" text NOT NULL, + "status" text, + "score" bigint NOT NULL, + "lentAt" timestamp without time zone NOT NULL, + "lentBy" text NOT NULL, + "returnedAt" timestamp without time zone, + "receivedBy" text, + "pupilId" bigint NOT NULL, + "isbn" bigint NOT NULL, + "libraryBookId" bigint NOT NULL +); + +-- +-- Class PupilData as table pupil_data +-- +CREATE TABLE "pupil_data" ( + "id" bigserial PRIMARY KEY, + "status" text NOT NULL, + "internalId" bigint NOT NULL, + "password" text, + "preSchoolMedicalId" bigint, + "kindergardenId" bigint, + "kindergardenData" json, + "preSchoolTestId" bigint, + "avatarId" bigint, + "avatarAuthId" bigint, + "publicMediaAuth" json NOT NULL, + "publicMediaAuthDocumentId" bigint, + "contact" text, + "communicationPupil" json, + "specialInformation" text, + "tutorInfo" json, + "afterSchoolCare" json, + "credit" bigint NOT NULL, + "creditEarned" bigint NOT NULL, + "schoolyearHeldBackAt" timestamp without time zone, + "swimmer" text, + "_kindergardenPupilsKindergardenId" bigint +); + +-- Indexes +CREATE INDEX "pupil_data_status_idx" ON "pupil_data" USING btree ("status", "internalId"); +CREATE UNIQUE INDEX "pupil_data_internal_id_idx" ON "pupil_data" USING btree ("internalId"); + +-- +-- Class PupilListEntry as table pupil_list_entry +-- +CREATE TABLE "pupil_list_entry" ( + "id" bigserial PRIMARY KEY, + "status" boolean, + "comment" text, + "entryBy" text, + "schoolListId" bigint NOT NULL, + "pupilId" bigint NOT NULL +); + +-- +-- Class PupilWorkbook as table pupil_workbook +-- +CREATE TABLE "pupil_workbook" ( + "id" bigserial PRIMARY KEY, + "isbn" bigint NOT NULL, + "comment" text, + "score" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "finishedAt" timestamp without time zone, + "pupilId" bigint NOT NULL, + "workbookId" bigint NOT NULL +); + +-- +-- Class Classroom as table room +-- +CREATE TABLE "room" ( + "id" bigserial PRIMARY KEY, + "roomCode" text NOT NULL, + "roomName" text NOT NULL +); + +-- +-- Class ScheduledLesson as table scheduled_lesson +-- +CREATE TABLE "scheduled_lesson" ( + "id" bigserial PRIMARY KEY, + "active" boolean NOT NULL, + "subjectId" bigint NOT NULL, + "scheduledAtId" bigint NOT NULL, + "timetableSlotOrder" bigint NOT NULL, + "timetableId" bigint NOT NULL, + "mainTeacherId" bigint NOT NULL, + "lessonId" text NOT NULL, + "roomId" bigint NOT NULL, + "lessonGroupId" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text, + "modifiedAt" timestamp without time zone, + "recordtest" json, + "_roomScheduledlessonsRoomId" bigint +); + +-- +-- Class ScheduledLessonTeacher as table scheduled_lesson_teacher +-- +CREATE TABLE "scheduled_lesson_teacher" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "scheduledLessonId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "scheduled_lesson_teacher_unique_idx" ON "scheduled_lesson_teacher" USING btree ("userId", "scheduledLessonId"); + +-- +-- Class SchoolData as table school_data +-- +CREATE TABLE "school_data" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "officialName" text NOT NULL, + "address" text NOT NULL, + "schoolNumber" text NOT NULL, + "telephoneNumber" text NOT NULL, + "email" text NOT NULL, + "website" text NOT NULL, + "logoId" bigint, + "officialSealId" bigint +); + +-- +-- Class SchoolList as table school_list +-- +CREATE TABLE "school_list" ( + "id" bigserial PRIMARY KEY, + "listId" text NOT NULL, + "archived" boolean NOT NULL, + "name" text NOT NULL, + "description" text NOT NULL, + "createdBy" text NOT NULL, + "public" boolean NOT NULL, + "authorizedUsers" text +); + +-- +-- Class SchoolSemester as table school_semester +-- +CREATE TABLE "school_semester" ( + "id" bigserial PRIMARY KEY, + "schoolYear" text NOT NULL, + "isFirst" boolean NOT NULL, + "startDate" timestamp without time zone NOT NULL, + "endDate" timestamp without time zone NOT NULL, + "classConferenceDate" timestamp without time zone, + "supportConferenceDate" timestamp without time zone, + "reportConferenceDate" timestamp without time zone, + "reportSignedDate" timestamp without time zone +); + +-- +-- Class Schoolday as table schoolday +-- +CREATE TABLE "schoolday" ( + "id" bigserial PRIMARY KEY, + "schoolday" timestamp without time zone NOT NULL, + "schoolSemesterId" bigint NOT NULL +); + +-- +-- Class SchooldayEvent as table schoolday_event +-- +CREATE TABLE "schoolday_event" ( + "id" bigserial PRIMARY KEY, + "eventId" text NOT NULL, + "eventType" text NOT NULL, + "eventReason" text NOT NULL, + "createdBy" text NOT NULL, + "processed" boolean NOT NULL, + "processedBy" text, + "processedAt" timestamp without time zone, + "documentId" bigint, + "processedDocumentId" bigint, + "schooldayId" bigint NOT NULL, + "pupilId" bigint NOT NULL +); + +-- +-- Class Subject as table subject +-- +CREATE TABLE "subject" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "name" text NOT NULL, + "description" text, + "color" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text NOT NULL +); + +-- +-- Class SupportCategory as table support_category +-- +CREATE TABLE "support_category" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "categoryId" bigint NOT NULL, + "parentCategory" bigint +); + +-- +-- Class SupportGoal as table support_category_goal +-- +CREATE TABLE "support_category_goal" ( + "id" bigserial PRIMARY KEY, + "goalId" text NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "score" bigint NOT NULL, + "achievedAt" timestamp without time zone, + "description" text NOT NULL, + "strategies" text NOT NULL, + "pupilId" bigint NOT NULL, + "supportCategoryId" bigint NOT NULL, + "_learningSupportPlanSupportgoalsLearningSupportPlanId" bigint, + "_supportCategoryCategorygoalsSupportCategoryId" bigint, + "_pupilDataSupportgoalsPupilDataId" bigint +); + +-- +-- Class SupportCategoryStatus as table support_category_status +-- +CREATE TABLE "support_category_status" ( + "id" bigserial PRIMARY KEY, + "score" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "comment" text NOT NULL, + "pupilId" bigint NOT NULL, + "supportCategoryId" bigint NOT NULL, + "learningSupportPlanId" bigint NOT NULL, + "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId" bigint, + "_supportCategoryCategorystatuesSupportCategoryId" bigint, + "_pupilDataSupportcategorystatusesPupilDataId" bigint +); + +-- +-- Class SupportGoalCheck as table support_goal_check +-- +CREATE TABLE "support_goal_check" ( + "id" bigserial PRIMARY KEY, + "checkId" text NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "score" bigint NOT NULL, + "comment" text NOT NULL, + "supportGoalId" bigint NOT NULL, + "_supportCategoryGoalGoalchecksSupportCategoryGoalId" bigint +); + +-- +-- Class SupportLevel as table support_level +-- +CREATE TABLE "support_level" ( + "id" bigserial PRIMARY KEY, + "level" bigint NOT NULL, + "comment" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "createdBy" text NOT NULL, + "pupilId" bigint NOT NULL +); + +-- +-- Class Timetable as table timetable +-- +CREATE TABLE "timetable" ( + "id" bigserial PRIMARY KEY, + "active" boolean NOT NULL, + "startsAt" timestamp without time zone NOT NULL, + "endsAt" timestamp without time zone, + "name" text NOT NULL, + "schoolSemesterId" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modified" json +); + +-- Indexes +CREATE INDEX "timetable_school_semester_idx" ON "timetable" USING btree ("schoolSemesterId"); + +-- +-- Class TimetableSlot as table timetable_slot +-- +CREATE TABLE "timetable_slot" ( + "id" bigserial PRIMARY KEY, + "day" text NOT NULL, + "startTime" text NOT NULL, + "endTime" text NOT NULL, + "timetableId" bigint NOT NULL +); + +-- +-- Class User as table user +-- +CREATE TABLE "user" ( + "id" bigserial PRIMARY KEY, + "userInfoId" bigint NOT NULL, + "role" text NOT NULL, + "matrixUserId" text, + "timeUnits" bigint NOT NULL, + "reliefTimeUnits" bigint NOT NULL, + "pupilsAuth" json, + "credit" bigint NOT NULL, + "userFlags" json NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "user_info_id_unique_idx" ON "user" USING btree ("userInfoId"); + +-- +-- Class UserDevice as table user_device +-- +CREATE TABLE "user_device" ( + "id" bigserial PRIMARY KEY, + "userInfoId" bigint NOT NULL, + "deviceId" text NOT NULL, + "deviceName" text NOT NULL, + "lastLogin" timestamp without time zone NOT NULL, + "isActive" boolean NOT NULL, + "authId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "auth_key_user_device_idx" ON "user_device" USING btree ("authId"); + +-- +-- Class Workbook as table workbook +-- +CREATE TABLE "workbook" ( + "id" bigserial PRIMARY KEY, + "isbn" bigint NOT NULL, + "name" text NOT NULL, + "subject" text, + "level" text, + "amount" bigint, + "imageUrl" text NOT NULL +); + +-- +-- Class CloudStorageEntry as table serverpod_cloud_storage +-- +CREATE TABLE "serverpod_cloud_storage" ( + "id" bigserial PRIMARY KEY, + "storageId" text NOT NULL, + "path" text NOT NULL, + "addedTime" timestamp without time zone NOT NULL, + "expiration" timestamp without time zone, + "byteData" bytea NOT NULL, + "verified" boolean NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_cloud_storage_path_idx" ON "serverpod_cloud_storage" USING btree ("storageId", "path"); +CREATE INDEX "serverpod_cloud_storage_expiration" ON "serverpod_cloud_storage" USING btree ("expiration"); + +-- +-- Class CloudStorageDirectUploadEntry as table serverpod_cloud_storage_direct_upload +-- +CREATE TABLE "serverpod_cloud_storage_direct_upload" ( + "id" bigserial PRIMARY KEY, + "storageId" text NOT NULL, + "path" text NOT NULL, + "expiration" timestamp without time zone NOT NULL, + "authKey" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_cloud_storage_direct_upload_storage_path" ON "serverpod_cloud_storage_direct_upload" USING btree ("storageId", "path"); + +-- +-- Class FutureCallEntry as table serverpod_future_call +-- +CREATE TABLE "serverpod_future_call" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "time" timestamp without time zone NOT NULL, + "serializedObject" text, + "serverId" text NOT NULL, + "identifier" text +); + +-- Indexes +CREATE INDEX "serverpod_future_call_time_idx" ON "serverpod_future_call" USING btree ("time"); +CREATE INDEX "serverpod_future_call_serverId_idx" ON "serverpod_future_call" USING btree ("serverId"); +CREATE INDEX "serverpod_future_call_identifier_idx" ON "serverpod_future_call" USING btree ("identifier"); + +-- +-- Class ServerHealthConnectionInfo as table serverpod_health_connection_info +-- +CREATE TABLE "serverpod_health_connection_info" ( + "id" bigserial PRIMARY KEY, + "serverId" text NOT NULL, + "timestamp" timestamp without time zone NOT NULL, + "active" bigint NOT NULL, + "closing" bigint NOT NULL, + "idle" bigint NOT NULL, + "granularity" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_health_connection_info_timestamp_idx" ON "serverpod_health_connection_info" USING btree ("timestamp", "serverId", "granularity"); + +-- +-- Class ServerHealthMetric as table serverpod_health_metric +-- +CREATE TABLE "serverpod_health_metric" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "serverId" text NOT NULL, + "timestamp" timestamp without time zone NOT NULL, + "isHealthy" boolean NOT NULL, + "value" double precision NOT NULL, + "granularity" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_health_metric_timestamp_idx" ON "serverpod_health_metric" USING btree ("timestamp", "serverId", "name", "granularity"); + +-- +-- Class LogEntry as table serverpod_log +-- +CREATE TABLE "serverpod_log" ( + "id" bigserial PRIMARY KEY, + "sessionLogId" bigint NOT NULL, + "messageId" bigint, + "reference" text, + "serverId" text NOT NULL, + "time" timestamp without time zone NOT NULL, + "logLevel" bigint NOT NULL, + "message" text NOT NULL, + "error" text, + "stackTrace" text, + "order" bigint NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_log_sessionLogId_idx" ON "serverpod_log" USING btree ("sessionLogId"); + +-- +-- Class MessageLogEntry as table serverpod_message_log +-- +CREATE TABLE "serverpod_message_log" ( + "id" bigserial PRIMARY KEY, + "sessionLogId" bigint NOT NULL, + "serverId" text NOT NULL, + "messageId" bigint NOT NULL, + "endpoint" text NOT NULL, + "messageName" text NOT NULL, + "duration" double precision NOT NULL, + "error" text, + "stackTrace" text, + "slow" boolean NOT NULL, + "order" bigint NOT NULL +); + +-- +-- Class MethodInfo as table serverpod_method +-- +CREATE TABLE "serverpod_method" ( + "id" bigserial PRIMARY KEY, + "endpoint" text NOT NULL, + "method" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_method_endpoint_method_idx" ON "serverpod_method" USING btree ("endpoint", "method"); + +-- +-- Class DatabaseMigrationVersion as table serverpod_migrations +-- +CREATE TABLE "serverpod_migrations" ( + "id" bigserial PRIMARY KEY, + "module" text NOT NULL, + "version" text NOT NULL, + "timestamp" timestamp without time zone +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_migrations_ids" ON "serverpod_migrations" USING btree ("module"); + +-- +-- Class QueryLogEntry as table serverpod_query_log +-- +CREATE TABLE "serverpod_query_log" ( + "id" bigserial PRIMARY KEY, + "serverId" text NOT NULL, + "sessionLogId" bigint NOT NULL, + "messageId" bigint, + "query" text NOT NULL, + "duration" double precision NOT NULL, + "numRows" bigint, + "error" text, + "stackTrace" text, + "slow" boolean NOT NULL, + "order" bigint NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_query_log_sessionLogId_idx" ON "serverpod_query_log" USING btree ("sessionLogId"); + +-- +-- Class ReadWriteTestEntry as table serverpod_readwrite_test +-- +CREATE TABLE "serverpod_readwrite_test" ( + "id" bigserial PRIMARY KEY, + "number" bigint NOT NULL +); + +-- +-- Class RuntimeSettings as table serverpod_runtime_settings +-- +CREATE TABLE "serverpod_runtime_settings" ( + "id" bigserial PRIMARY KEY, + "logSettings" json NOT NULL, + "logSettingsOverrides" json NOT NULL, + "logServiceCalls" boolean NOT NULL, + "logMalformedCalls" boolean NOT NULL +); + +-- +-- Class SessionLogEntry as table serverpod_session_log +-- +CREATE TABLE "serverpod_session_log" ( + "id" bigserial PRIMARY KEY, + "serverId" text NOT NULL, + "time" timestamp without time zone NOT NULL, + "module" text, + "endpoint" text, + "method" text, + "duration" double precision, + "numQueries" bigint, + "slow" boolean, + "error" text, + "stackTrace" text, + "authenticatedUserId" bigint, + "isOpen" boolean, + "touched" timestamp without time zone NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_session_log_serverid_idx" ON "serverpod_session_log" USING btree ("serverId"); +CREATE INDEX "serverpod_session_log_touched_idx" ON "serverpod_session_log" USING btree ("touched"); +CREATE INDEX "serverpod_session_log_isopen_idx" ON "serverpod_session_log" USING btree ("isOpen"); + +-- +-- Class AuthKey as table serverpod_auth_key +-- +CREATE TABLE "serverpod_auth_key" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "hash" text NOT NULL, + "scopeNames" json NOT NULL, + "method" text NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_auth_key_userId_idx" ON "serverpod_auth_key" USING btree ("userId"); + +-- +-- Class EmailAuth as table serverpod_email_auth +-- +CREATE TABLE "serverpod_email_auth" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "email" text NOT NULL, + "hash" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_email_auth_email" ON "serverpod_email_auth" USING btree ("email"); + +-- +-- Class EmailCreateAccountRequest as table serverpod_email_create_request +-- +CREATE TABLE "serverpod_email_create_request" ( + "id" bigserial PRIMARY KEY, + "userName" text NOT NULL, + "email" text NOT NULL, + "hash" text NOT NULL, + "verificationCode" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_email_auth_create_account_request_idx" ON "serverpod_email_create_request" USING btree ("email"); + +-- +-- Class EmailFailedSignIn as table serverpod_email_failed_sign_in +-- +CREATE TABLE "serverpod_email_failed_sign_in" ( + "id" bigserial PRIMARY KEY, + "email" text NOT NULL, + "time" timestamp without time zone NOT NULL, + "ipAddress" text NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_email_failed_sign_in_email_idx" ON "serverpod_email_failed_sign_in" USING btree ("email"); +CREATE INDEX "serverpod_email_failed_sign_in_time_idx" ON "serverpod_email_failed_sign_in" USING btree ("time"); + +-- +-- Class EmailReset as table serverpod_email_reset +-- +CREATE TABLE "serverpod_email_reset" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "verificationCode" text NOT NULL, + "expiration" timestamp without time zone NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_email_reset_verification_idx" ON "serverpod_email_reset" USING btree ("verificationCode"); + +-- +-- Class GoogleRefreshToken as table serverpod_google_refresh_token +-- +CREATE TABLE "serverpod_google_refresh_token" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "refreshToken" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_google_refresh_token_userId_idx" ON "serverpod_google_refresh_token" USING btree ("userId"); + +-- +-- Class UserImage as table serverpod_user_image +-- +CREATE TABLE "serverpod_user_image" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "version" bigint NOT NULL, + "url" text NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_user_image_user_id" ON "serverpod_user_image" USING btree ("userId", "version"); + +-- +-- Class UserInfo as table serverpod_user_info +-- +CREATE TABLE "serverpod_user_info" ( + "id" bigserial PRIMARY KEY, + "userIdentifier" text NOT NULL, + "userName" text, + "fullName" text, + "email" text, + "created" timestamp without time zone NOT NULL, + "imageUrl" text, + "scopeNames" json NOT NULL, + "blocked" boolean NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_user_info_user_identifier" ON "serverpod_user_info" USING btree ("userIdentifier"); +CREATE INDEX "serverpod_user_info_email" ON "serverpod_user_info" USING btree ("email"); + +-- +-- Foreign relations for "book_tagging" table +-- +ALTER TABLE ONLY "book_tagging" + ADD CONSTRAINT "book_tagging_fk_0" + FOREIGN KEY("bookId") + REFERENCES "book"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "book_tagging" + ADD CONSTRAINT "book_tagging_fk_1" + FOREIGN KEY("bookTagId") + REFERENCES "book_tag"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "competence_check" table +-- +ALTER TABLE ONLY "competence_check" + ADD CONSTRAINT "competence_check_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_check" + ADD CONSTRAINT "competence_check_fk_1" + FOREIGN KEY("competenceId") + REFERENCES "competence"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "competence_goal" table +-- +ALTER TABLE ONLY "competence_goal" + ADD CONSTRAINT "competence_goal_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_goal" + ADD CONSTRAINT "competence_goal_fk_1" + FOREIGN KEY("competenceId") + REFERENCES "competence"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "competence_report" table +-- +ALTER TABLE ONLY "competence_report" + ADD CONSTRAINT "competence_report_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_report" + ADD CONSTRAINT "competence_report_fk_1" + FOREIGN KEY("schoolSemesterId") + REFERENCES "school_semester"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "competence_report_check" table +-- +ALTER TABLE ONLY "competence_report_check" + ADD CONSTRAINT "competence_report_check_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_report_check" + ADD CONSTRAINT "competence_report_check_fk_1" + FOREIGN KEY("competenceId") + REFERENCES "competence_report_item"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_report_check" + ADD CONSTRAINT "competence_report_check_fk_2" + FOREIGN KEY("competenceReportId") + REFERENCES "competence_report"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "credit_transaction" table +-- +ALTER TABLE ONLY "credit_transaction" + ADD CONSTRAINT "credit_transaction_fk_0" + FOREIGN KEY("_pupilDataCredittransactionsPupilDataId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "hub_document" table +-- +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_0" + FOREIGN KEY("_pupilBookLendingPupilbooklendingfilesPupilBookLendingId") + REFERENCES "pupil_book_lending"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_1" + FOREIGN KEY("_competenceCheckDocumentsCompetenceCheckId") + REFERENCES "competence_check"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_2" + FOREIGN KEY("_competenceGoalDocumentsCompetenceGoalId") + REFERENCES "competence_goal"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_3" + FOREIGN KEY("_supportCategoryStatusDocumentsSupportCategoryStatusId") + REFERENCES "support_category_status"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_4" + FOREIGN KEY("_supportGoalCheckDocumentsSupportGoalCheckId") + REFERENCES "support_goal_check"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_5" + FOREIGN KEY("_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId") + REFERENCES "pre_school_medical"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_6" + FOREIGN KEY("_preSchoolTestPreschooltestdocumentsPreSchoolTestId") + REFERENCES "pre_school_test"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "learning_support_plan" table +-- +ALTER TABLE ONLY "learning_support_plan" + ADD CONSTRAINT "learning_support_plan_fk_0" + FOREIGN KEY("learningSupportLevelId") + REFERENCES "support_level"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "learning_support_plan" + ADD CONSTRAINT "learning_support_plan_fk_1" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "learning_support_plan" + ADD CONSTRAINT "learning_support_plan_fk_2" + FOREIGN KEY("schoolSemesterId") + REFERENCES "school_semester"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson" table +-- +ALTER TABLE ONLY "lesson" + ADD CONSTRAINT "lesson_fk_0" + FOREIGN KEY("subjectId") + REFERENCES "subject"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson_attendance" table +-- +ALTER TABLE ONLY "lesson_attendance" + ADD CONSTRAINT "lesson_attendance_fk_0" + FOREIGN KEY("lessonId") + REFERENCES "lesson"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "lesson_attendance" + ADD CONSTRAINT "lesson_attendance_fk_1" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson_group" table +-- +ALTER TABLE ONLY "lesson_group" + ADD CONSTRAINT "lesson_group_fk_0" + FOREIGN KEY("timetableId") + REFERENCES "timetable"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson_group_pupil" table +-- +ALTER TABLE ONLY "lesson_group_pupil" + ADD CONSTRAINT "lesson_group_pupil_fk_0" + FOREIGN KEY("lessonGroupId") + REFERENCES "lesson_group"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "lesson_group_pupil" + ADD CONSTRAINT "lesson_group_pupil_fk_1" + FOREIGN KEY("pupilDataId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson_teacher" table +-- +ALTER TABLE ONLY "lesson_teacher" + ADD CONSTRAINT "lesson_teacher_fk_0" + FOREIGN KEY("userId") + REFERENCES "user"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "lesson_teacher" + ADD CONSTRAINT "lesson_teacher_fk_1" + FOREIGN KEY("scheduledLessonId") + REFERENCES "lesson"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "library_book" table +-- +ALTER TABLE ONLY "library_book" + ADD CONSTRAINT "library_book_fk_0" + FOREIGN KEY("bookId") + REFERENCES "book"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "library_book" + ADD CONSTRAINT "library_book_fk_1" + FOREIGN KEY("locationId") + REFERENCES "library_book_location"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "missed_class" table +-- +ALTER TABLE ONLY "missed_class" + ADD CONSTRAINT "missed_class_fk_0" + FOREIGN KEY("schooldayId") + REFERENCES "schoolday"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "missed_class" + ADD CONSTRAINT "missed_class_fk_1" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_authorization" table +-- +ALTER TABLE ONLY "pupil_authorization" + ADD CONSTRAINT "pupil_authorization_fk_0" + FOREIGN KEY("fileId") + REFERENCES "hub_document"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_authorization" + ADD CONSTRAINT "pupil_authorization_fk_1" + FOREIGN KEY("authorizationId") + REFERENCES "authorization"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_authorization" + ADD CONSTRAINT "pupil_authorization_fk_2" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_book_lending" table +-- +ALTER TABLE ONLY "pupil_book_lending" + ADD CONSTRAINT "pupil_book_lending_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_book_lending" + ADD CONSTRAINT "pupil_book_lending_fk_1" + FOREIGN KEY("libraryBookId") + REFERENCES "library_book"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_data" table +-- +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_0" + FOREIGN KEY("preSchoolMedicalId") + REFERENCES "pre_school_medical"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_1" + FOREIGN KEY("kindergardenId") + REFERENCES "kindergarden"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_2" + FOREIGN KEY("preSchoolTestId") + REFERENCES "pre_school_test"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_3" + FOREIGN KEY("avatarId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_4" + FOREIGN KEY("avatarAuthId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_5" + FOREIGN KEY("publicMediaAuthDocumentId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_6" + FOREIGN KEY("_kindergardenPupilsKindergardenId") + REFERENCES "kindergarden"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_list_entry" table +-- +ALTER TABLE ONLY "pupil_list_entry" + ADD CONSTRAINT "pupil_list_entry_fk_0" + FOREIGN KEY("schoolListId") + REFERENCES "school_list"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_list_entry" + ADD CONSTRAINT "pupil_list_entry_fk_1" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_workbook" table +-- +ALTER TABLE ONLY "pupil_workbook" + ADD CONSTRAINT "pupil_workbook_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_workbook" + ADD CONSTRAINT "pupil_workbook_fk_1" + FOREIGN KEY("workbookId") + REFERENCES "workbook"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "scheduled_lesson" table +-- +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_0" + FOREIGN KEY("subjectId") + REFERENCES "subject"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_1" + FOREIGN KEY("scheduledAtId") + REFERENCES "timetable_slot"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_2" + FOREIGN KEY("timetableId") + REFERENCES "timetable"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_3" + FOREIGN KEY("roomId") + REFERENCES "room"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_4" + FOREIGN KEY("lessonGroupId") + REFERENCES "lesson_group"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_5" + FOREIGN KEY("_roomScheduledlessonsRoomId") + REFERENCES "room"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "scheduled_lesson_teacher" table +-- +ALTER TABLE ONLY "scheduled_lesson_teacher" + ADD CONSTRAINT "scheduled_lesson_teacher_fk_0" + FOREIGN KEY("userId") + REFERENCES "user"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson_teacher" + ADD CONSTRAINT "scheduled_lesson_teacher_fk_1" + FOREIGN KEY("scheduledLessonId") + REFERENCES "scheduled_lesson"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "school_data" table +-- +ALTER TABLE ONLY "school_data" + ADD CONSTRAINT "school_data_fk_0" + FOREIGN KEY("logoId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "school_data" + ADD CONSTRAINT "school_data_fk_1" + FOREIGN KEY("officialSealId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "schoolday" table +-- +ALTER TABLE ONLY "schoolday" + ADD CONSTRAINT "schoolday_fk_0" + FOREIGN KEY("schoolSemesterId") + REFERENCES "school_semester"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "schoolday_event" table +-- +ALTER TABLE ONLY "schoolday_event" + ADD CONSTRAINT "schoolday_event_fk_0" + FOREIGN KEY("documentId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "schoolday_event" + ADD CONSTRAINT "schoolday_event_fk_1" + FOREIGN KEY("processedDocumentId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "schoolday_event" + ADD CONSTRAINT "schoolday_event_fk_2" + FOREIGN KEY("schooldayId") + REFERENCES "schoolday"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "schoolday_event" + ADD CONSTRAINT "schoolday_event_fk_3" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "support_category_goal" table +-- +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_1" + FOREIGN KEY("supportCategoryId") + REFERENCES "support_category"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_2" + FOREIGN KEY("_learningSupportPlanSupportgoalsLearningSupportPlanId") + REFERENCES "learning_support_plan"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_3" + FOREIGN KEY("_supportCategoryCategorygoalsSupportCategoryId") + REFERENCES "support_category"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_4" + FOREIGN KEY("_pupilDataSupportgoalsPupilDataId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "support_category_status" table +-- +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_1" + FOREIGN KEY("supportCategoryId") + REFERENCES "support_category"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_2" + FOREIGN KEY("learningSupportPlanId") + REFERENCES "learning_support_plan"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_3" + FOREIGN KEY("_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId") + REFERENCES "learning_support_plan"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_4" + FOREIGN KEY("_supportCategoryCategorystatuesSupportCategoryId") + REFERENCES "support_category"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_5" + FOREIGN KEY("_pupilDataSupportcategorystatusesPupilDataId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "support_goal_check" table +-- +ALTER TABLE ONLY "support_goal_check" + ADD CONSTRAINT "support_goal_check_fk_0" + FOREIGN KEY("supportGoalId") + REFERENCES "support_category_goal"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_goal_check" + ADD CONSTRAINT "support_goal_check_fk_1" + FOREIGN KEY("_supportCategoryGoalGoalchecksSupportCategoryGoalId") + REFERENCES "support_category_goal"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "support_level" table +-- +ALTER TABLE ONLY "support_level" + ADD CONSTRAINT "support_level_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "timetable" table +-- +ALTER TABLE ONLY "timetable" + ADD CONSTRAINT "timetable_fk_0" + FOREIGN KEY("schoolSemesterId") + REFERENCES "school_semester"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "timetable_slot" table +-- +ALTER TABLE ONLY "timetable_slot" + ADD CONSTRAINT "timetable_slot_fk_0" + FOREIGN KEY("timetableId") + REFERENCES "timetable"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "user" table +-- +ALTER TABLE ONLY "user" + ADD CONSTRAINT "user_fk_0" + FOREIGN KEY("userInfoId") + REFERENCES "serverpod_user_info"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "user_device" table +-- +ALTER TABLE ONLY "user_device" + ADD CONSTRAINT "user_device_fk_0" + FOREIGN KEY("userInfoId") + REFERENCES "serverpod_user_info"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "user_device" + ADD CONSTRAINT "user_device_fk_1" + FOREIGN KEY("authId") + REFERENCES "serverpod_auth_key"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "serverpod_log" table +-- +ALTER TABLE ONLY "serverpod_log" + ADD CONSTRAINT "serverpod_log_fk_0" + FOREIGN KEY("sessionLogId") + REFERENCES "serverpod_session_log"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "serverpod_message_log" table +-- +ALTER TABLE ONLY "serverpod_message_log" + ADD CONSTRAINT "serverpod_message_log_fk_0" + FOREIGN KEY("sessionLogId") + REFERENCES "serverpod_session_log"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "serverpod_query_log" table +-- +ALTER TABLE ONLY "serverpod_query_log" + ADD CONSTRAINT "serverpod_query_log_fk_0" + FOREIGN KEY("sessionLogId") + REFERENCES "serverpod_session_log"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + + +-- +-- MIGRATION VERSION FOR school_data_hub +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('school_data_hub', '20251121184735230', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20251121184735230', "timestamp" = now(); + +-- +-- MIGRATION VERSION FOR serverpod +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('serverpod', '20240516151843329', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20240516151843329', "timestamp" = now(); + +-- +-- MIGRATION VERSION FOR serverpod_auth +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('serverpod_auth', '20240520102713718', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20240520102713718', "timestamp" = now(); + + +COMMIT; diff --git a/school_data_hub_server/migrations/20251121184735230/definition_project.json b/school_data_hub_server/migrations/20251121184735230/definition_project.json new file mode 100644 index 00000000..f705ec57 --- /dev/null +++ b/school_data_hub_server/migrations/20251121184735230/definition_project.json @@ -0,0 +1,4827 @@ +{ + "moduleName": "school_data_hub", + "tables": [ + { + "name": "authorization", + "dartName": "Authorization", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('authorization_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "authorization_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "book", + "dartName": "Book", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "title", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "author", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "readingLevel", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "imagePath", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "book_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "book_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "isbn" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "book_tag", + "dartName": "BookTag", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_tag_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "book_tag_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "book_tagging", + "dartName": "BookTagging", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_tagging_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "bookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "bookTagId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "book_tagging_fk_0", + "columns": [ + "bookId" + ], + "referenceTable": "book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "book_tagging_fk_1", + "columns": [ + "bookTagId" + ], + "referenceTable": "book_tag", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "book_tagging_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "book_tagging_index_idx", + "elements": [ + { + "type": 0, + "definition": "bookId" + }, + { + "type": 0, + "definition": "bookTagId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "competence", + "dartName": "Competence", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentCompetence", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "level", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "indicators", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "competence_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_check", + "dartName": "CompetenceCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "checkId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "valueFactor", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "groupCheckId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "groupCheckName", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_check_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_check_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_goal", + "dartName": "CompetenceGoal", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_goal_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "strategies", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "score", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_goal_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_goal_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_goal_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report", + "dartName": "CompetenceReport", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "reportId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievement", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_report_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_fk_1", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_report_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report_check", + "dartName": "CompetenceReportCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievement", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceReportId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_report_check_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_check_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence_report_item", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_check_fk_2", + "columns": [ + "competenceReportId" + ], + "referenceTable": "competence_report", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_report_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report_item", + "dartName": "CompetenceReportItem", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_item_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentItem", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "level", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "competence_report_item_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "compulsory_room", + "dartName": "CompulsoryRoom", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('compulsory_room_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "roomId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:MatrixRoomType" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "compulsory_room_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "credit_transaction", + "dartName": "CreditTransaction", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('credit_transaction_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "sender", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "receiver", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "amount", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "dateTime", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "description", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "_pupilDataCredittransactionsPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "credit_transaction_fk_0", + "columns": [ + "_pupilDataCredittransactionsPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "credit_transaction_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "reciever_idx", + "elements": [ + { + "type": 0, + "definition": "receiver" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "sender_idx", + "elements": [ + { + "type": 0, + "definition": "sender" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "hub_document", + "dartName": "HubDocument", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('hub_document_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "documentId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "documentPath", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_competenceCheckDocumentsCompetenceCheckId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_competenceGoalDocumentsCompetenceGoalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryStatusDocumentsSupportCategoryStatusId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportGoalCheckDocumentsSupportGoalCheckId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_preSchoolTestPreschooltestdocumentsPreSchoolTestId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "hub_document_fk_0", + "columns": [ + "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId" + ], + "referenceTable": "pupil_book_lending", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_1", + "columns": [ + "_competenceCheckDocumentsCompetenceCheckId" + ], + "referenceTable": "competence_check", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_2", + "columns": [ + "_competenceGoalDocumentsCompetenceGoalId" + ], + "referenceTable": "competence_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_3", + "columns": [ + "_supportCategoryStatusDocumentsSupportCategoryStatusId" + ], + "referenceTable": "support_category_status", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_4", + "columns": [ + "_supportGoalCheckDocumentsSupportGoalCheckId" + ], + "referenceTable": "support_goal_check", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_5", + "columns": [ + "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId" + ], + "referenceTable": "pre_school_medical", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_6", + "columns": [ + "_preSchoolTestPreschooltestdocumentsPreSchoolTestId" + ], + "referenceTable": "pre_school_test", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "hub_document_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "kindergarden", + "dartName": "Kindergarden", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('kindergarden_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "phone", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "address", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "contactPerson", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "kindergarden_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "last_pupil_identities_update", + "dartName": "LastPupilIdentiesUpdate", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('last_pupil_identities_update_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "date", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "last_pupil_identities_update_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "learning_support_plan", + "dartName": "LearningSupportPlan", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('learning_support_plan_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "planId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "number", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "socialPedagogue", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "proffesionalsInvolved", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "strengthsDescription", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "problemsDescription", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "learningSupportLevelId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "learning_support_plan_fk_0", + "columns": [ + "learningSupportLevelId" + ], + "referenceTable": "support_level", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "learning_support_plan_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "learning_support_plan_fk_2", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "learning_support_plan_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson", + "dartName": "Lesson", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "subjectId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_fk_0", + "columns": [ + "subjectId" + ], + "referenceTable": "subject", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_attendance", + "dartName": "LessonAttendance", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_attendance_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_attendance_fk_0", + "columns": [ + "lessonId" + ], + "referenceTable": "lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_attendance_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_attendance_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_group", + "dartName": "LessonGroup", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_group_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "color", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_group_fk_0", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_group_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_group_pupil", + "dartName": "ScheduledLessonGroupMembership", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_group_pupil_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lessonGroupId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilDataId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_group_pupil_fk_0", + "columns": [ + "lessonGroupId" + ], + "referenceTable": "lesson_group", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_group_pupil_fk_1", + "columns": [ + "pupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_group_pupil_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "lesson_group_membership_index_idx", + "elements": [ + { + "type": 0, + "definition": "lessonGroupId" + }, + { + "type": 0, + "definition": "pupilDataId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "lesson_teacher", + "dartName": "LessonTeacher", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_teacher_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledLessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_teacher_fk_0", + "columns": [ + "userId" + ], + "referenceTable": "user", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_teacher_fk_1", + "columns": [ + "scheduledLessonId" + ], + "referenceTable": "lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_teacher_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "lesson_teacher_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "scheduledLessonId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "library_book", + "dartName": "LibraryBook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('library_book_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "libraryId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "bookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "locationId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "available", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [ + { + "constraintName": "library_book_fk_0", + "columns": [ + "bookId" + ], + "referenceTable": "book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "library_book_fk_1", + "columns": [ + "locationId" + ], + "referenceTable": "library_book_location", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "library_book_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "library_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "libraryId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "library_book_location", + "dartName": "LibraryBookLocation", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('library_book_location_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "location", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "library_book_location_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "location_unique_idx", + "elements": [ + { + "type": 0, + "definition": "location" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "missed_class", + "dartName": "MissedSchoolday", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('missed_class_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "missedType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:MissedType" + }, + { + "name": "unexcused", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "contacted", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:ContactedType" + }, + { + "name": "returned", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "returnedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "writtenExcuse", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "minutesLate", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "schooldayId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "missed_class_fk_0", + "columns": [ + "schooldayId" + ], + "referenceTable": "schoolday", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "missed_class_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "missed_class_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "schoolday_pupil_data_idx", + "elements": [ + { + "type": 0, + "definition": "schooldayId" + }, + { + "type": 0, + "definition": "pupilId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "pre_school_medical", + "dartName": "PreSchoolMedical", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pre_school_medical_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "preschoolMedicalStatus", + "columnType": 0, + "isNullable": true, + "dartType": "protocol:PreSchoolMedicalStatus?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "updatedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "updatedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "pre_school_medical_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pre_school_test", + "dartName": "PreSchoolTest", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pre_school_test_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "careNeedsIntensity", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "pre_school_test_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_authorization", + "dartName": "PupilAuthorization", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_authorization_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "fileId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "authorizationId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_authorization_fk_0", + "columns": [ + "fileId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_authorization_fk_1", + "columns": [ + "authorizationId" + ], + "referenceTable": "authorization", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_authorization_fk_2", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_authorization_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_book_lending", + "dartName": "PupilBookLending", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_book_lending_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lendingId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "status", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lentAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "lentBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "returnedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "receivedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "libraryBookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_book_lending_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_book_lending_fk_1", + "columns": [ + "libraryBookId" + ], + "referenceTable": "library_book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_book_lending_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_data", + "dartName": "PupilData", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_data_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:PupilStatus" + }, + { + "name": "internalId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "password", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "preSchoolMedicalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "kindergardenId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "kindergardenData", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:KindergardenInfo?" + }, + { + "name": "preSchoolTestId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "avatarId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "avatarAuthId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "publicMediaAuth", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:PublicMediaAuth" + }, + { + "name": "publicMediaAuthDocumentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "contact", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "communicationPupil", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:CommunicationSkills?" + }, + { + "name": "specialInformation", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "tutorInfo", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:TutorInfo?" + }, + { + "name": "afterSchoolCare", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:AfterSchoolCare?" + }, + { + "name": "credit", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "creditEarned", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolyearHeldBackAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "swimmer", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "_kindergardenPupilsKindergardenId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_data_fk_0", + "columns": [ + "preSchoolMedicalId" + ], + "referenceTable": "pre_school_medical", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_1", + "columns": [ + "kindergardenId" + ], + "referenceTable": "kindergarden", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_2", + "columns": [ + "preSchoolTestId" + ], + "referenceTable": "pre_school_test", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_3", + "columns": [ + "avatarId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_4", + "columns": [ + "avatarAuthId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_5", + "columns": [ + "publicMediaAuthDocumentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_6", + "columns": [ + "_kindergardenPupilsKindergardenId" + ], + "referenceTable": "kindergarden", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_data_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "pupil_data_status_idx", + "elements": [ + { + "type": 0, + "definition": "status" + }, + { + "type": 0, + "definition": "internalId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "pupil_data_internal_id_idx", + "elements": [ + { + "type": 0, + "definition": "internalId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "pupil_list_entry", + "dartName": "PupilListEntry", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_list_entry_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "entryBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "schoolListId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_list_entry_fk_0", + "columns": [ + "schoolListId" + ], + "referenceTable": "school_list", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_list_entry_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "pupil_list_entry_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_workbook", + "dartName": "PupilWorkbook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_workbook_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "finishedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "workbookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_workbook_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_workbook_fk_1", + "columns": [ + "workbookId" + ], + "referenceTable": "workbook", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "pupil_workbook_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "room", + "dartName": "Classroom", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('room_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "roomCode", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "room_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "scheduled_lesson", + "dartName": "ScheduledLesson", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('scheduled_lesson_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "active", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "subjectId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledAtId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "timetableSlotOrder", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "mainTeacherId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lessonId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lessonGroupId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "recordtest", + "columnType": 8, + "isNullable": true, + "dartType": "( {int testint, String testString})?" + }, + { + "name": "_roomScheduledlessonsRoomId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "scheduled_lesson_fk_0", + "columns": [ + "subjectId" + ], + "referenceTable": "subject", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_1", + "columns": [ + "scheduledAtId" + ], + "referenceTable": "timetable_slot", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_2", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_3", + "columns": [ + "roomId" + ], + "referenceTable": "room", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_4", + "columns": [ + "lessonGroupId" + ], + "referenceTable": "lesson_group", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_5", + "columns": [ + "_roomScheduledlessonsRoomId" + ], + "referenceTable": "room", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "scheduled_lesson_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "scheduled_lesson_teacher", + "dartName": "ScheduledLessonTeacher", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('scheduled_lesson_teacher_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledLessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "scheduled_lesson_teacher_fk_0", + "columns": [ + "userId" + ], + "referenceTable": "user", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_teacher_fk_1", + "columns": [ + "scheduledLessonId" + ], + "referenceTable": "scheduled_lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "scheduled_lesson_teacher_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "scheduled_lesson_teacher_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "scheduledLessonId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "school_data", + "dartName": "SchoolData", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_data_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "officialName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "address", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "schoolNumber", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "telephoneNumber", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "website", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "logoId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "officialSealId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "school_data_fk_0", + "columns": [ + "logoId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "school_data_fk_1", + "columns": [ + "officialSealId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "school_data_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "school_list", + "dartName": "SchoolList", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_list_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "listId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "archived", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "public", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "authorizedUsers", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "school_list_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "school_semester", + "dartName": "SchoolSemester", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_semester_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "schoolYear", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "isFirst", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "startDate", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "endDate", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "classConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "supportConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "reportConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "reportSignedDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "school_semester_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "schoolday", + "dartName": "Schoolday", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('schoolday_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "schoolday", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "schoolday_fk_0", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "schoolday_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "schoolday_event", + "dartName": "SchooldayEvent", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('schoolday_event_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "eventId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "eventType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:SchooldayEventType" + }, + { + "name": "eventReason", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "processed", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "processedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "processedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "documentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "processedDocumentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "schooldayId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "schoolday_event_fk_0", + "columns": [ + "documentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_1", + "columns": [ + "processedDocumentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_2", + "columns": [ + "schooldayId" + ], + "referenceTable": "schoolday", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_3", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "schoolday_event_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "subject", + "dartName": "Subject", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('subject_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "color", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "subject_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category", + "dartName": "SupportCategory", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "categoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentCategory", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "support_category_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category_goal", + "dartName": "SupportGoal", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_goal_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "goalId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "strategies", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "supportCategoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_learningSupportPlanSupportgoalsLearningSupportPlanId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryCategorygoalsSupportCategoryId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_pupilDataSupportgoalsPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_category_goal_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_1", + "columns": [ + "supportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "support_category_goal_fk_2", + "columns": [ + "_learningSupportPlanSupportgoalsLearningSupportPlanId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_3", + "columns": [ + "_supportCategoryCategorygoalsSupportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_4", + "columns": [ + "_pupilDataSupportgoalsPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_category_goal_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category_status", + "dartName": "SupportCategoryStatus", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_status_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "supportCategoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "learningSupportPlanId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryCategorystatuesSupportCategoryId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_pupilDataSupportcategorystatusesPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_category_status_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_1", + "columns": [ + "supportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_2", + "columns": [ + "learningSupportPlanId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_3", + "columns": [ + "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_4", + "columns": [ + "_supportCategoryCategorystatuesSupportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_5", + "columns": [ + "_pupilDataSupportcategorystatusesPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_category_status_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_goal_check", + "dartName": "SupportGoalCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_goal_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "checkId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "supportGoalId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_supportCategoryGoalGoalchecksSupportCategoryGoalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_goal_check_fk_0", + "columns": [ + "supportGoalId" + ], + "referenceTable": "support_category_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "support_goal_check_fk_1", + "columns": [ + "_supportCategoryGoalGoalchecksSupportCategoryGoalId" + ], + "referenceTable": "support_category_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_goal_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_level", + "dartName": "SupportLevel", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_level_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "level", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "support_level_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "support_level_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "timetable", + "dartName": "Timetable", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('timetable_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "active", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "startsAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "endsAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modified", + "columnType": 8, + "isNullable": true, + "dartType": "List<( {String modifiedBy, DateTime modifiedAt})>?" + } + ], + "foreignKeys": [ + { + "constraintName": "timetable_fk_0", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "timetable_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "timetable_school_semester_idx", + "elements": [ + { + "type": 0, + "definition": "schoolSemesterId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "timetable_slot", + "dartName": "TimetableSlot", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('timetable_slot_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "day", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:Weekday" + }, + { + "name": "startTime", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "endTime", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "timetable_slot_fk_0", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "timetable_slot_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "user", + "dartName": "User", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('user_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userInfoId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "role", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:Role" + }, + { + "name": "matrixUserId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "timeUnits", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "reliefTimeUnits", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilsAuth", + "columnType": 8, + "isNullable": true, + "dartType": "Set?" + }, + { + "name": "credit", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "userFlags", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:UserFlags" + } + ], + "foreignKeys": [ + { + "constraintName": "user_fk_0", + "columns": [ + "userInfoId" + ], + "referenceTable": "serverpod_user_info", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "user_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "user_info_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userInfoId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "user_device", + "dartName": "UserDevice", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('user_device_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userInfoId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "deviceId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "deviceName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "lastLogin", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "isActive", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "authId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "user_device_fk_0", + "columns": [ + "userInfoId" + ], + "referenceTable": "serverpod_user_info", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "user_device_fk_1", + "columns": [ + "authId" + ], + "referenceTable": "serverpod_auth_key", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "user_device_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "auth_key_user_device_idx", + "elements": [ + { + "type": 0, + "definition": "authId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "workbook", + "dartName": "Workbook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('workbook_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "subject", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "level", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "amount", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "imageUrl", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "workbook_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + } + ], + "installedModules": [ + { + "module": "serverpod", + "version": "20240516151843329" + }, + { + "module": "serverpod_auth", + "version": "20240520102713718" + } + ], + "migrationApiVersion": 1 +} \ No newline at end of file diff --git a/school_data_hub_server/migrations/20251121184735230/migration.json b/school_data_hub_server/migrations/20251121184735230/migration.json new file mode 100644 index 00000000..d8ba4f95 --- /dev/null +++ b/school_data_hub_server/migrations/20251121184735230/migration.json @@ -0,0 +1,28 @@ +{ + "actions": [ + { + "type": "alterTable", + "alterTable": { + "name": "user", + "schema": "public", + "addColumns": [ + { + "name": "matrixUserId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + } + ], + "deleteColumns": [], + "modifyColumns": [], + "addIndexes": [], + "deleteIndexes": [], + "addForeignKeys": [], + "deleteForeignKeys": [], + "warnings": [] + } + } + ], + "warnings": [], + "migrationApiVersion": 1 +} \ No newline at end of file diff --git a/school_data_hub_server/migrations/20251121184735230/migration.sql b/school_data_hub_server/migrations/20251121184735230/migration.sql new file mode 100644 index 00000000..08d886a7 --- /dev/null +++ b/school_data_hub_server/migrations/20251121184735230/migration.sql @@ -0,0 +1,33 @@ +BEGIN; + +-- +-- ACTION ALTER TABLE +-- +ALTER TABLE "user" ADD COLUMN "matrixUserId" text; + +-- +-- MIGRATION VERSION FOR school_data_hub +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('school_data_hub', '20251121184735230', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20251121184735230', "timestamp" = now(); + +-- +-- MIGRATION VERSION FOR serverpod +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('serverpod', '20240516151843329', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20240516151843329', "timestamp" = now(); + +-- +-- MIGRATION VERSION FOR serverpod_auth +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('serverpod_auth', '20240520102713718', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20240520102713718', "timestamp" = now(); + + +COMMIT; diff --git a/school_data_hub_server/migrations/20251123092602494/definition.json b/school_data_hub_server/migrations/20251123092602494/definition.json new file mode 100644 index 00000000..aaa4102e --- /dev/null +++ b/school_data_hub_server/migrations/20251123092602494/definition.json @@ -0,0 +1,6526 @@ +{ + "moduleName": "school_data_hub", + "tables": [ + { + "name": "authorization", + "dartName": "Authorization", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('authorization_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "authorization_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "book", + "dartName": "Book", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "title", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "author", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "readingLevel", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "imagePath", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "book_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "book_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "isbn" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "book_tag", + "dartName": "BookTag", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_tag_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "book_tag_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "book_tagging", + "dartName": "BookTagging", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_tagging_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "bookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "bookTagId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "book_tagging_fk_0", + "columns": [ + "bookId" + ], + "referenceTable": "book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "book_tagging_fk_1", + "columns": [ + "bookTagId" + ], + "referenceTable": "book_tag", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "book_tagging_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "book_tagging_index_idx", + "elements": [ + { + "type": 0, + "definition": "bookId" + }, + { + "type": 0, + "definition": "bookTagId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "competence", + "dartName": "Competence", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentCompetence", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "level", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "indicators", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "competence_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_check", + "dartName": "CompetenceCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "checkId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "valueFactor", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "groupCheckId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "groupCheckName", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_check_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_check_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_goal", + "dartName": "CompetenceGoal", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_goal_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "strategies", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "score", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_goal_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_goal_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_goal_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report", + "dartName": "CompetenceReport", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "reportId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievement", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_report_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_fk_1", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_report_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report_check", + "dartName": "CompetenceReportCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievement", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceReportId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_report_check_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_check_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence_report_item", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_check_fk_2", + "columns": [ + "competenceReportId" + ], + "referenceTable": "competence_report", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_report_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report_item", + "dartName": "CompetenceReportItem", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_item_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentItem", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "level", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "competence_report_item_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "compulsory_room", + "dartName": "CompulsoryRoom", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('compulsory_room_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "roomId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:MatrixRoomType" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "compulsory_room_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "credit_transaction", + "dartName": "CreditTransaction", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('credit_transaction_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "sender", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "receiver", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "amount", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "dateTime", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "description", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "_pupilDataCredittransactionsPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "credit_transaction_fk_0", + "columns": [ + "_pupilDataCredittransactionsPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "credit_transaction_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "reciever_idx", + "elements": [ + { + "type": 0, + "definition": "receiver" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "sender_idx", + "elements": [ + { + "type": 0, + "definition": "sender" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "hub_document", + "dartName": "HubDocument", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('hub_document_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "documentId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "documentPath", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_competenceCheckDocumentsCompetenceCheckId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_competenceGoalDocumentsCompetenceGoalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryStatusDocumentsSupportCategoryStatusId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportGoalCheckDocumentsSupportGoalCheckId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_preSchoolTestPreschooltestdocumentsPreSchoolTestId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "hub_document_fk_0", + "columns": [ + "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId" + ], + "referenceTable": "pupil_book_lending", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_1", + "columns": [ + "_competenceCheckDocumentsCompetenceCheckId" + ], + "referenceTable": "competence_check", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_2", + "columns": [ + "_competenceGoalDocumentsCompetenceGoalId" + ], + "referenceTable": "competence_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_3", + "columns": [ + "_supportCategoryStatusDocumentsSupportCategoryStatusId" + ], + "referenceTable": "support_category_status", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_4", + "columns": [ + "_supportGoalCheckDocumentsSupportGoalCheckId" + ], + "referenceTable": "support_goal_check", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_5", + "columns": [ + "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId" + ], + "referenceTable": "pre_school_medical", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_6", + "columns": [ + "_preSchoolTestPreschooltestdocumentsPreSchoolTestId" + ], + "referenceTable": "pre_school_test", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "hub_document_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "kindergarden", + "dartName": "Kindergarden", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('kindergarden_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "phone", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "address", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "contactPerson", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "kindergarden_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "last_pupil_identities_update", + "dartName": "LastPupilIdentiesUpdate", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('last_pupil_identities_update_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "date", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "last_pupil_identities_update_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "learning_support_plan", + "dartName": "LearningSupportPlan", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('learning_support_plan_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "planId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "number", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "socialPedagogue", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "proffesionalsInvolved", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "strengthsDescription", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "problemsDescription", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "learningSupportLevelId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "learning_support_plan_fk_0", + "columns": [ + "learningSupportLevelId" + ], + "referenceTable": "support_level", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "learning_support_plan_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "learning_support_plan_fk_2", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "learning_support_plan_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson", + "dartName": "Lesson", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "subjectId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_fk_0", + "columns": [ + "subjectId" + ], + "referenceTable": "subject", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_attendance", + "dartName": "LessonAttendance", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_attendance_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_attendance_fk_0", + "columns": [ + "lessonId" + ], + "referenceTable": "lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_attendance_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_attendance_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_group", + "dartName": "LessonGroup", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_group_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "color", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_group_fk_0", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_group_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_group_pupil", + "dartName": "ScheduledLessonGroupMembership", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_group_pupil_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lessonGroupId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilDataId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_group_pupil_fk_0", + "columns": [ + "lessonGroupId" + ], + "referenceTable": "lesson_group", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_group_pupil_fk_1", + "columns": [ + "pupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_group_pupil_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "lesson_group_membership_index_idx", + "elements": [ + { + "type": 0, + "definition": "lessonGroupId" + }, + { + "type": 0, + "definition": "pupilDataId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "lesson_teacher", + "dartName": "LessonTeacher", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_teacher_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledLessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_teacher_fk_0", + "columns": [ + "userId" + ], + "referenceTable": "user", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_teacher_fk_1", + "columns": [ + "scheduledLessonId" + ], + "referenceTable": "lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_teacher_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "lesson_teacher_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "scheduledLessonId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "library_book", + "dartName": "LibraryBook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('library_book_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "libraryId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "bookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "locationId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "available", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [ + { + "constraintName": "library_book_fk_0", + "columns": [ + "bookId" + ], + "referenceTable": "book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "library_book_fk_1", + "columns": [ + "locationId" + ], + "referenceTable": "library_book_location", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "library_book_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "library_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "libraryId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "library_book_location", + "dartName": "LibraryBookLocation", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('library_book_location_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "location", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "library_book_location_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "location_unique_idx", + "elements": [ + { + "type": 0, + "definition": "location" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "missed_class", + "dartName": "MissedSchoolday", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('missed_class_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "missedType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:MissedType" + }, + { + "name": "unexcused", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "contacted", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:ContactedType" + }, + { + "name": "returned", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "returnedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "writtenExcuse", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "minutesLate", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "schooldayId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "missed_class_fk_0", + "columns": [ + "schooldayId" + ], + "referenceTable": "schoolday", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "missed_class_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "missed_class_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "schoolday_pupil_data_idx", + "elements": [ + { + "type": 0, + "definition": "schooldayId" + }, + { + "type": 0, + "definition": "pupilId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "pre_school_medical", + "dartName": "PreSchoolMedical", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pre_school_medical_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "preschoolMedicalStatus", + "columnType": 0, + "isNullable": true, + "dartType": "protocol:PreSchoolMedicalStatus?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "updatedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "updatedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "pre_school_medical_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pre_school_test", + "dartName": "PreSchoolTest", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pre_school_test_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "careNeedsIntensity", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "pre_school_test_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_authorization", + "dartName": "PupilAuthorization", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_authorization_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "fileId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "authorizationId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_authorization_fk_0", + "columns": [ + "fileId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_authorization_fk_1", + "columns": [ + "authorizationId" + ], + "referenceTable": "authorization", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_authorization_fk_2", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_authorization_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_book_lending", + "dartName": "PupilBookLending", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_book_lending_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lendingId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "status", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lentAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "lentBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "returnedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "receivedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "libraryBookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_book_lending_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_book_lending_fk_1", + "columns": [ + "libraryBookId" + ], + "referenceTable": "library_book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_book_lending_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_data", + "dartName": "PupilData", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_data_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:PupilStatus" + }, + { + "name": "internalId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "password", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "preSchoolMedicalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "kindergardenId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "kindergardenData", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:KindergardenInfo?" + }, + { + "name": "preSchoolTestId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "avatarId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "avatarAuthId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "publicMediaAuth", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:PublicMediaAuth" + }, + { + "name": "publicMediaAuthDocumentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "contact", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "communicationPupil", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:CommunicationSkills?" + }, + { + "name": "specialInformation", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "tutorInfo", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:TutorInfo?" + }, + { + "name": "afterSchoolCare", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:AfterSchoolCare?" + }, + { + "name": "credit", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "creditEarned", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolyearHeldBackAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "swimmer", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "_kindergardenPupilsKindergardenId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_data_fk_0", + "columns": [ + "preSchoolMedicalId" + ], + "referenceTable": "pre_school_medical", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_1", + "columns": [ + "kindergardenId" + ], + "referenceTable": "kindergarden", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_2", + "columns": [ + "preSchoolTestId" + ], + "referenceTable": "pre_school_test", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_3", + "columns": [ + "avatarId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_4", + "columns": [ + "avatarAuthId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_5", + "columns": [ + "publicMediaAuthDocumentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_6", + "columns": [ + "_kindergardenPupilsKindergardenId" + ], + "referenceTable": "kindergarden", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_data_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "pupil_data_status_idx", + "elements": [ + { + "type": 0, + "definition": "status" + }, + { + "type": 0, + "definition": "internalId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "pupil_data_internal_id_idx", + "elements": [ + { + "type": 0, + "definition": "internalId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "pupil_list_entry", + "dartName": "PupilListEntry", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_list_entry_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "entryBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "schoolListId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_list_entry_fk_0", + "columns": [ + "schoolListId" + ], + "referenceTable": "school_list", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_list_entry_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "pupil_list_entry_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_workbook", + "dartName": "PupilWorkbook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_workbook_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "finishedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "workbookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_workbook_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_workbook_fk_1", + "columns": [ + "workbookId" + ], + "referenceTable": "workbook", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "pupil_workbook_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "room", + "dartName": "Classroom", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('room_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "roomCode", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "room_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "scheduled_lesson", + "dartName": "ScheduledLesson", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('scheduled_lesson_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "active", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "subjectId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledAtId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "timetableSlotOrder", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "mainTeacherId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lessonId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lessonGroupId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "recordtest", + "columnType": 8, + "isNullable": true, + "dartType": "( {int testint, String testString})?" + }, + { + "name": "_roomScheduledlessonsRoomId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "scheduled_lesson_fk_0", + "columns": [ + "subjectId" + ], + "referenceTable": "subject", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_1", + "columns": [ + "scheduledAtId" + ], + "referenceTable": "timetable_slot", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_2", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_3", + "columns": [ + "roomId" + ], + "referenceTable": "room", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_4", + "columns": [ + "lessonGroupId" + ], + "referenceTable": "lesson_group", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_5", + "columns": [ + "_roomScheduledlessonsRoomId" + ], + "referenceTable": "room", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "scheduled_lesson_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "scheduled_lesson_teacher", + "dartName": "ScheduledLessonTeacher", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('scheduled_lesson_teacher_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledLessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "scheduled_lesson_teacher_fk_0", + "columns": [ + "userId" + ], + "referenceTable": "user", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_teacher_fk_1", + "columns": [ + "scheduledLessonId" + ], + "referenceTable": "scheduled_lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "scheduled_lesson_teacher_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "scheduled_lesson_teacher_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "scheduledLessonId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "school_data", + "dartName": "SchoolData", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_data_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "officialName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "address", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "schoolNumber", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "telephoneNumber", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "website", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "logoId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "officialSealId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "school_data_fk_0", + "columns": [ + "logoId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "school_data_fk_1", + "columns": [ + "officialSealId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "school_data_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "school_list", + "dartName": "SchoolList", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_list_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "listId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "archived", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "public", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "authorizedUsers", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "school_list_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "school_semester", + "dartName": "SchoolSemester", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_semester_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "schoolYear", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "isFirst", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "startDate", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "endDate", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "classConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "supportConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "reportConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "reportSignedDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "school_semester_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "schoolday", + "dartName": "Schoolday", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('schoolday_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "schoolday", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "schoolday_fk_0", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "schoolday_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "schoolday_event", + "dartName": "SchooldayEvent", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('schoolday_event_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "eventId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "eventType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:SchooldayEventType" + }, + { + "name": "eventReason", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "processed", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "processedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "processedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "documentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "processedDocumentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "schooldayId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "schoolday_event_fk_0", + "columns": [ + "documentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_1", + "columns": [ + "processedDocumentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_2", + "columns": [ + "schooldayId" + ], + "referenceTable": "schoolday", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_3", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "schoolday_event_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "subject", + "dartName": "Subject", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('subject_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "color", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "subject_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category", + "dartName": "SupportCategory", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "categoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentCategory", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "support_category_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category_goal", + "dartName": "SupportGoal", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_goal_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "goalId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "strategies", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "supportCategoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_learningSupportPlanSupportgoalsLearningSupportPlanId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryCategorygoalsSupportCategoryId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_pupilDataSupportgoalsPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_category_goal_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_1", + "columns": [ + "supportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "support_category_goal_fk_2", + "columns": [ + "_learningSupportPlanSupportgoalsLearningSupportPlanId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_3", + "columns": [ + "_supportCategoryCategorygoalsSupportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_4", + "columns": [ + "_pupilDataSupportgoalsPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_category_goal_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category_status", + "dartName": "SupportCategoryStatus", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_status_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "supportCategoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "learningSupportPlanId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryCategorystatuesSupportCategoryId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_pupilDataSupportcategorystatusesPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_category_status_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_1", + "columns": [ + "supportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_2", + "columns": [ + "learningSupportPlanId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_3", + "columns": [ + "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_4", + "columns": [ + "_supportCategoryCategorystatuesSupportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_5", + "columns": [ + "_pupilDataSupportcategorystatusesPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_category_status_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_goal_check", + "dartName": "SupportGoalCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_goal_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "checkId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "supportGoalId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_supportCategoryGoalGoalchecksSupportCategoryGoalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_goal_check_fk_0", + "columns": [ + "supportGoalId" + ], + "referenceTable": "support_category_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "support_goal_check_fk_1", + "columns": [ + "_supportCategoryGoalGoalchecksSupportCategoryGoalId" + ], + "referenceTable": "support_category_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_goal_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_level", + "dartName": "SupportLevel", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_level_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "level", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "support_level_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "support_level_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "timetable", + "dartName": "Timetable", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('timetable_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "active", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "startsAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "endsAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modified", + "columnType": 8, + "isNullable": true, + "dartType": "List<( {String modifiedBy, DateTime modifiedAt})>?" + } + ], + "foreignKeys": [ + { + "constraintName": "timetable_fk_0", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "timetable_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "timetable_school_semester_idx", + "elements": [ + { + "type": 0, + "definition": "schoolSemesterId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "timetable_slot", + "dartName": "TimetableSlot", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('timetable_slot_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "day", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:Weekday" + }, + { + "name": "startTime", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "endTime", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "timetable_slot_fk_0", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "timetable_slot_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "user", + "dartName": "User", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('user_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userInfoId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "role", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:Role" + }, + { + "name": "matrixUserId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "timeUnits", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "reliefTimeUnits", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilsAuth", + "columnType": 8, + "isNullable": true, + "dartType": "Set?" + }, + { + "name": "schooldayEventsProcessingTeam", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "credit", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "userFlags", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:UserFlags" + } + ], + "foreignKeys": [ + { + "constraintName": "user_fk_0", + "columns": [ + "userInfoId" + ], + "referenceTable": "serverpod_user_info", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "user_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "user_info_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userInfoId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "user_device", + "dartName": "UserDevice", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('user_device_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userInfoId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "deviceId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "deviceName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "lastLogin", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "isActive", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "authId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "user_device_fk_0", + "columns": [ + "userInfoId" + ], + "referenceTable": "serverpod_user_info", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "user_device_fk_1", + "columns": [ + "authId" + ], + "referenceTable": "serverpod_auth_key", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "user_device_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "auth_key_user_device_idx", + "elements": [ + { + "type": 0, + "definition": "authId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "workbook", + "dartName": "Workbook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('workbook_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "subject", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "level", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "amount", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "imageUrl", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "workbook_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "serverpod_cloud_storage", + "dartName": "CloudStorageEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_cloud_storage_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "storageId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "path", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "addedTime", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "expiration", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "byteData", + "columnType": 5, + "isNullable": false, + "dartType": "dart:typed_data:ByteData" + }, + { + "name": "verified", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_cloud_storage_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_cloud_storage_path_idx", + "elements": [ + { + "type": 0, + "definition": "storageId" + }, + { + "type": 0, + "definition": "path" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + }, + { + "indexName": "serverpod_cloud_storage_expiration", + "elements": [ + { + "type": 0, + "definition": "expiration" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_cloud_storage_direct_upload", + "dartName": "CloudStorageDirectUploadEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_cloud_storage_direct_upload_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "storageId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "path", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "expiration", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "authKey", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_cloud_storage_direct_upload_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_cloud_storage_direct_upload_storage_path", + "elements": [ + { + "type": 0, + "definition": "storageId" + }, + { + "type": 0, + "definition": "path" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_future_call", + "dartName": "FutureCallEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_future_call_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "time", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "serializedObject", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "identifier", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_future_call_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_future_call_time_idx", + "elements": [ + { + "type": 0, + "definition": "time" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_future_call_serverId_idx", + "elements": [ + { + "type": 0, + "definition": "serverId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_future_call_identifier_idx", + "elements": [ + { + "type": 0, + "definition": "identifier" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_health_connection_info", + "dartName": "ServerHealthConnectionInfo", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_health_connection_info_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timestamp", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "active", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "closing", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "idle", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "granularity", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_health_connection_info_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_health_connection_info_timestamp_idx", + "elements": [ + { + "type": 0, + "definition": "timestamp" + }, + { + "type": 0, + "definition": "serverId" + }, + { + "type": 0, + "definition": "granularity" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_health_metric", + "dartName": "ServerHealthMetric", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_health_metric_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timestamp", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "isHealthy", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "value", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "granularity", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_health_metric_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_health_metric_timestamp_idx", + "elements": [ + { + "type": 0, + "definition": "timestamp" + }, + { + "type": 0, + "definition": "serverId" + }, + { + "type": 0, + "definition": "name" + }, + { + "type": 0, + "definition": "granularity" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_log", + "dartName": "LogEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_log_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "sessionLogId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "messageId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "reference", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "time", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "logLevel", + "columnType": 6, + "isNullable": false, + "dartType": "protocol:LogLevel" + }, + { + "name": "message", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "error", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "stackTrace", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "serverpod_log_fk_0", + "columns": [ + "sessionLogId" + ], + "referenceTable": "serverpod_session_log", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "serverpod_log_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_log_sessionLogId_idx", + "elements": [ + { + "type": 0, + "definition": "sessionLogId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_message_log", + "dartName": "MessageLogEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_message_log_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "sessionLogId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "messageId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "endpoint", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "messageName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "duration", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "error", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "stackTrace", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "slow", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "order", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "serverpod_message_log_fk_0", + "columns": [ + "sessionLogId" + ], + "referenceTable": "serverpod_session_log", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "serverpod_message_log_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "serverpod_method", + "dartName": "MethodInfo", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_method_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "endpoint", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "method", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_method_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_method_endpoint_method_idx", + "elements": [ + { + "type": 0, + "definition": "endpoint" + }, + { + "type": 0, + "definition": "method" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_migrations", + "dartName": "DatabaseMigrationVersion", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_migrations_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "module", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "version", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timestamp", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_migrations_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_migrations_ids", + "elements": [ + { + "type": 0, + "definition": "module" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_query_log", + "dartName": "QueryLogEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_query_log_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "sessionLogId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "messageId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "query", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "duration", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "numRows", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "error", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "stackTrace", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "slow", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "order", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "serverpod_query_log_fk_0", + "columns": [ + "sessionLogId" + ], + "referenceTable": "serverpod_session_log", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "serverpod_query_log_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_query_log_sessionLogId_idx", + "elements": [ + { + "type": 0, + "definition": "sessionLogId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_readwrite_test", + "dartName": "ReadWriteTestEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_readwrite_test_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "number", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_readwrite_test_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "serverpod_runtime_settings", + "dartName": "RuntimeSettings", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_runtime_settings_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "logSettings", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:LogSettings" + }, + { + "name": "logSettingsOverrides", + "columnType": 8, + "isNullable": false, + "dartType": "List" + }, + { + "name": "logServiceCalls", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "logMalformedCalls", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_runtime_settings_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "serverpod_session_log", + "dartName": "SessionLogEntry", + "module": "serverpod", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_session_log_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "serverId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "time", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "module", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "endpoint", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "method", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "duration", + "columnType": 3, + "isNullable": true, + "dartType": "double?" + }, + { + "name": "numQueries", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "slow", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "error", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "stackTrace", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "authenticatedUserId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "isOpen", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "touched", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_session_log_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_session_log_serverid_idx", + "elements": [ + { + "type": 0, + "definition": "serverId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_session_log_touched_idx", + "elements": [ + { + "type": 0, + "definition": "touched" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_session_log_isopen_idx", + "elements": [ + { + "type": 0, + "definition": "isOpen" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_auth_key", + "dartName": "AuthKey", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_auth_key_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "hash", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "scopeNames", + "columnType": 8, + "isNullable": false, + "dartType": "List" + }, + { + "name": "method", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_auth_key_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_auth_key_userId_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_email_auth", + "dartName": "EmailAuth", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_email_auth_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "hash", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_email_auth_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_email_auth_email", + "elements": [ + { + "type": 0, + "definition": "email" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_email_create_request", + "dartName": "EmailCreateAccountRequest", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_email_create_request_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "hash", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "verificationCode", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_email_create_request_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_email_auth_create_account_request_idx", + "elements": [ + { + "type": 0, + "definition": "email" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_email_failed_sign_in", + "dartName": "EmailFailedSignIn", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_email_failed_sign_in_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "time", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "ipAddress", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_email_failed_sign_in_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_email_failed_sign_in_email_idx", + "elements": [ + { + "type": 0, + "definition": "email" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "serverpod_email_failed_sign_in_time_idx", + "elements": [ + { + "type": 0, + "definition": "time" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_email_reset", + "dartName": "EmailReset", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_email_reset_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "verificationCode", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "expiration", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_email_reset_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_email_reset_verification_idx", + "elements": [ + { + "type": 0, + "definition": "verificationCode" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_google_refresh_token", + "dartName": "GoogleRefreshToken", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_google_refresh_token_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "refreshToken", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_google_refresh_token_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_google_refresh_token_userId_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_user_image", + "dartName": "UserImage", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_user_image_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "version", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "url", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_user_image_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_user_image_user_id", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "version" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "serverpod_user_info", + "dartName": "UserInfo", + "module": "serverpod_auth", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('serverpod_user_info_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userIdentifier", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "userName", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "fullName", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "email", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "created", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "imageUrl", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "scopeNames", + "columnType": 8, + "isNullable": false, + "dartType": "List" + }, + { + "name": "blocked", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "serverpod_user_info_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "serverpod_user_info_user_identifier", + "elements": [ + { + "type": 0, + "definition": "userIdentifier" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + }, + { + "indexName": "serverpod_user_info_email", + "elements": [ + { + "type": 0, + "definition": "email" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + } + ], + "installedModules": [ + { + "module": "school_data_hub", + "version": "20251123092602494" + }, + { + "module": "serverpod", + "version": "20240516151843329" + }, + { + "module": "serverpod_auth", + "version": "20240520102713718" + } + ], + "migrationApiVersion": 1 +} \ No newline at end of file diff --git a/school_data_hub_server/migrations/20251123092602494/definition.sql b/school_data_hub_server/migrations/20251123092602494/definition.sql new file mode 100644 index 00000000..108f2291 --- /dev/null +++ b/school_data_hub_server/migrations/20251123092602494/definition.sql @@ -0,0 +1,1727 @@ +BEGIN; + +-- +-- Class Authorization as table authorization +-- +CREATE TABLE "authorization" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "description" text NOT NULL, + "createdBy" text NOT NULL +); + +-- +-- Class Book as table book +-- +CREATE TABLE "book" ( + "id" bigserial PRIMARY KEY, + "isbn" bigint NOT NULL, + "title" text NOT NULL, + "author" text NOT NULL, + "description" text NOT NULL, + "readingLevel" text, + "imagePath" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "book_id_unique_idx" ON "book" USING btree ("isbn"); + +-- +-- Class BookTag as table book_tag +-- +CREATE TABLE "book_tag" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL +); + +-- +-- Class BookTagging as table book_tagging +-- +CREATE TABLE "book_tagging" ( + "id" bigserial PRIMARY KEY, + "bookId" bigint NOT NULL, + "bookTagId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "book_tagging_index_idx" ON "book_tagging" USING btree ("bookId", "bookTagId"); + +-- +-- Class Competence as table competence +-- +CREATE TABLE "competence" ( + "id" bigserial PRIMARY KEY, + "publicId" bigint NOT NULL, + "parentCompetence" bigint, + "name" text NOT NULL, + "level" json, + "indicators" json, + "order" bigint +); + +-- +-- Class CompetenceCheck as table competence_check +-- +CREATE TABLE "competence_check" ( + "id" bigserial PRIMARY KEY, + "checkId" text NOT NULL, + "score" bigint NOT NULL, + "comment" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "valueFactor" double precision NOT NULL, + "groupCheckId" text, + "groupCheckName" text, + "pupilId" bigint NOT NULL, + "competenceId" bigint NOT NULL +); + +-- +-- Class CompetenceGoal as table competence_goal +-- +CREATE TABLE "competence_goal" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "description" text NOT NULL, + "strategies" json, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text NOT NULL, + "score" bigint, + "achievedAt" timestamp without time zone NOT NULL, + "pupilId" bigint NOT NULL, + "competenceId" bigint NOT NULL +); + +-- +-- Class CompetenceReport as table competence_report +-- +CREATE TABLE "competence_report" ( + "id" bigserial PRIMARY KEY, + "reportId" text NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text NOT NULL, + "achievement" text NOT NULL, + "achievedAt" timestamp without time zone NOT NULL, + "pupilId" bigint NOT NULL, + "schoolSemesterId" bigint NOT NULL +); + +-- +-- Class CompetenceReportCheck as table competence_report_check +-- +CREATE TABLE "competence_report_check" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "achievement" bigint NOT NULL, + "comment" text NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "pupilId" bigint NOT NULL, + "competenceId" bigint NOT NULL, + "competenceReportId" bigint NOT NULL +); + +-- +-- Class CompetenceReportItem as table competence_report_item +-- +CREATE TABLE "competence_report_item" ( + "id" bigserial PRIMARY KEY, + "publicId" bigint NOT NULL, + "parentItem" bigint, + "name" text NOT NULL, + "level" json, + "order" bigint +); + +-- +-- Class CompulsoryRoom as table compulsory_room +-- +CREATE TABLE "compulsory_room" ( + "id" bigserial PRIMARY KEY, + "roomId" text NOT NULL, + "roomType" text NOT NULL +); + +-- +-- Class CreditTransaction as table credit_transaction +-- +CREATE TABLE "credit_transaction" ( + "id" bigserial PRIMARY KEY, + "sender" text NOT NULL, + "receiver" bigint NOT NULL, + "amount" bigint NOT NULL, + "dateTime" timestamp without time zone NOT NULL, + "description" text, + "_pupilDataCredittransactionsPupilDataId" bigint +); + +-- Indexes +CREATE INDEX "reciever_idx" ON "credit_transaction" USING btree ("receiver"); +CREATE INDEX "sender_idx" ON "credit_transaction" USING btree ("sender"); + +-- +-- Class HubDocument as table hub_document +-- +CREATE TABLE "hub_document" ( + "id" bigserial PRIMARY KEY, + "documentId" text NOT NULL, + "documentPath" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId" bigint, + "_competenceCheckDocumentsCompetenceCheckId" bigint, + "_competenceGoalDocumentsCompetenceGoalId" bigint, + "_supportCategoryStatusDocumentsSupportCategoryStatusId" bigint, + "_supportGoalCheckDocumentsSupportGoalCheckId" bigint, + "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId" bigint, + "_preSchoolTestPreschooltestdocumentsPreSchoolTestId" bigint +); + +-- +-- Class Kindergarden as table kindergarden +-- +CREATE TABLE "kindergarden" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "phone" text NOT NULL, + "address" text NOT NULL, + "email" text NOT NULL, + "contactPerson" text NOT NULL +); + +-- +-- Class LastPupilIdentiesUpdate as table last_pupil_identities_update +-- +CREATE TABLE "last_pupil_identities_update" ( + "id" bigserial PRIMARY KEY, + "date" timestamp without time zone +); + +-- +-- Class LearningSupportPlan as table learning_support_plan +-- +CREATE TABLE "learning_support_plan" ( + "id" bigserial PRIMARY KEY, + "planId" text NOT NULL, + "number" bigint, + "createdBy" text NOT NULL, + "socialPedagogue" text, + "proffesionalsInvolved" text, + "strengthsDescription" text, + "problemsDescription" text, + "learningSupportLevelId" bigint NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "comment" text, + "pupilId" bigint NOT NULL, + "schoolSemesterId" bigint NOT NULL +); + +-- +-- Class Lesson as table lesson +-- +CREATE TABLE "lesson" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "subjectId" bigint NOT NULL +); + +-- +-- Class LessonAttendance as table lesson_attendance +-- +CREATE TABLE "lesson_attendance" ( + "id" bigserial PRIMARY KEY, + "lessonId" bigint NOT NULL, + "pupilId" bigint NOT NULL, + "comment" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text NOT NULL, + "modifiedAt" timestamp without time zone NOT NULL +); + +-- +-- Class LessonGroup as table lesson_group +-- +CREATE TABLE "lesson_group" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "name" text NOT NULL, + "color" text, + "timetableId" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text, + "modifiedAt" timestamp without time zone +); + +-- +-- Class ScheduledLessonGroupMembership as table lesson_group_pupil +-- +CREATE TABLE "lesson_group_pupil" ( + "id" bigserial PRIMARY KEY, + "lessonGroupId" bigint NOT NULL, + "pupilDataId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "lesson_group_membership_index_idx" ON "lesson_group_pupil" USING btree ("lessonGroupId", "pupilDataId"); + +-- +-- Class LessonTeacher as table lesson_teacher +-- +CREATE TABLE "lesson_teacher" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "scheduledLessonId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "lesson_teacher_unique_idx" ON "lesson_teacher" USING btree ("userId", "scheduledLessonId"); + +-- +-- Class LibraryBook as table library_book +-- +CREATE TABLE "library_book" ( + "id" bigserial PRIMARY KEY, + "libraryId" text NOT NULL, + "bookId" bigint NOT NULL, + "locationId" bigint NOT NULL, + "available" boolean NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "library_id_unique_idx" ON "library_book" USING btree ("libraryId"); + +-- +-- Class LibraryBookLocation as table library_book_location +-- +CREATE TABLE "library_book_location" ( + "id" bigserial PRIMARY KEY, + "location" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "location_unique_idx" ON "library_book_location" USING btree ("location"); + +-- +-- Class MissedSchoolday as table missed_class +-- +CREATE TABLE "missed_class" ( + "id" bigserial PRIMARY KEY, + "missedType" text NOT NULL, + "unexcused" boolean NOT NULL, + "contacted" text NOT NULL, + "returned" boolean NOT NULL, + "returnedAt" timestamp without time zone, + "writtenExcuse" boolean NOT NULL, + "minutesLate" bigint, + "createdBy" text NOT NULL, + "modifiedBy" text, + "comment" text, + "schooldayId" bigint NOT NULL, + "pupilId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "schoolday_pupil_data_idx" ON "missed_class" USING btree ("schooldayId", "pupilId"); + +-- +-- Class PreSchoolMedical as table pre_school_medical +-- +CREATE TABLE "pre_school_medical" ( + "id" bigserial PRIMARY KEY, + "preschoolMedicalStatus" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "updatedBy" text, + "updatedAt" timestamp without time zone +); + +-- +-- Class PreSchoolTest as table pre_school_test +-- +CREATE TABLE "pre_school_test" ( + "id" bigserial PRIMARY KEY, + "careNeedsIntensity" bigint +); + +-- +-- Class PupilAuthorization as table pupil_authorization +-- +CREATE TABLE "pupil_authorization" ( + "id" bigserial PRIMARY KEY, + "status" boolean, + "comment" text, + "createdBy" text, + "fileId" bigint, + "authorizationId" bigint NOT NULL, + "pupilId" bigint NOT NULL +); + +-- +-- Class PupilBookLending as table pupil_book_lending +-- +CREATE TABLE "pupil_book_lending" ( + "id" bigserial PRIMARY KEY, + "lendingId" text NOT NULL, + "status" text, + "score" bigint NOT NULL, + "lentAt" timestamp without time zone NOT NULL, + "lentBy" text NOT NULL, + "returnedAt" timestamp without time zone, + "receivedBy" text, + "pupilId" bigint NOT NULL, + "isbn" bigint NOT NULL, + "libraryBookId" bigint NOT NULL +); + +-- +-- Class PupilData as table pupil_data +-- +CREATE TABLE "pupil_data" ( + "id" bigserial PRIMARY KEY, + "status" text NOT NULL, + "internalId" bigint NOT NULL, + "password" text, + "preSchoolMedicalId" bigint, + "kindergardenId" bigint, + "kindergardenData" json, + "preSchoolTestId" bigint, + "avatarId" bigint, + "avatarAuthId" bigint, + "publicMediaAuth" json NOT NULL, + "publicMediaAuthDocumentId" bigint, + "contact" text, + "communicationPupil" json, + "specialInformation" text, + "tutorInfo" json, + "afterSchoolCare" json, + "credit" bigint NOT NULL, + "creditEarned" bigint NOT NULL, + "schoolyearHeldBackAt" timestamp without time zone, + "swimmer" text, + "_kindergardenPupilsKindergardenId" bigint +); + +-- Indexes +CREATE INDEX "pupil_data_status_idx" ON "pupil_data" USING btree ("status", "internalId"); +CREATE UNIQUE INDEX "pupil_data_internal_id_idx" ON "pupil_data" USING btree ("internalId"); + +-- +-- Class PupilListEntry as table pupil_list_entry +-- +CREATE TABLE "pupil_list_entry" ( + "id" bigserial PRIMARY KEY, + "status" boolean, + "comment" text, + "entryBy" text, + "schoolListId" bigint NOT NULL, + "pupilId" bigint NOT NULL +); + +-- +-- Class PupilWorkbook as table pupil_workbook +-- +CREATE TABLE "pupil_workbook" ( + "id" bigserial PRIMARY KEY, + "isbn" bigint NOT NULL, + "comment" text, + "score" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "finishedAt" timestamp without time zone, + "pupilId" bigint NOT NULL, + "workbookId" bigint NOT NULL +); + +-- +-- Class Classroom as table room +-- +CREATE TABLE "room" ( + "id" bigserial PRIMARY KEY, + "roomCode" text NOT NULL, + "roomName" text NOT NULL +); + +-- +-- Class ScheduledLesson as table scheduled_lesson +-- +CREATE TABLE "scheduled_lesson" ( + "id" bigserial PRIMARY KEY, + "active" boolean NOT NULL, + "subjectId" bigint NOT NULL, + "scheduledAtId" bigint NOT NULL, + "timetableSlotOrder" bigint NOT NULL, + "timetableId" bigint NOT NULL, + "mainTeacherId" bigint NOT NULL, + "lessonId" text NOT NULL, + "roomId" bigint NOT NULL, + "lessonGroupId" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text, + "modifiedAt" timestamp without time zone, + "recordtest" json, + "_roomScheduledlessonsRoomId" bigint +); + +-- +-- Class ScheduledLessonTeacher as table scheduled_lesson_teacher +-- +CREATE TABLE "scheduled_lesson_teacher" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "scheduledLessonId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "scheduled_lesson_teacher_unique_idx" ON "scheduled_lesson_teacher" USING btree ("userId", "scheduledLessonId"); + +-- +-- Class SchoolData as table school_data +-- +CREATE TABLE "school_data" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "officialName" text NOT NULL, + "address" text NOT NULL, + "schoolNumber" text NOT NULL, + "telephoneNumber" text NOT NULL, + "email" text NOT NULL, + "website" text NOT NULL, + "logoId" bigint, + "officialSealId" bigint +); + +-- +-- Class SchoolList as table school_list +-- +CREATE TABLE "school_list" ( + "id" bigserial PRIMARY KEY, + "listId" text NOT NULL, + "archived" boolean NOT NULL, + "name" text NOT NULL, + "description" text NOT NULL, + "createdBy" text NOT NULL, + "public" boolean NOT NULL, + "authorizedUsers" text +); + +-- +-- Class SchoolSemester as table school_semester +-- +CREATE TABLE "school_semester" ( + "id" bigserial PRIMARY KEY, + "schoolYear" text NOT NULL, + "isFirst" boolean NOT NULL, + "startDate" timestamp without time zone NOT NULL, + "endDate" timestamp without time zone NOT NULL, + "classConferenceDate" timestamp without time zone, + "supportConferenceDate" timestamp without time zone, + "reportConferenceDate" timestamp without time zone, + "reportSignedDate" timestamp without time zone +); + +-- +-- Class Schoolday as table schoolday +-- +CREATE TABLE "schoolday" ( + "id" bigserial PRIMARY KEY, + "schoolday" timestamp without time zone NOT NULL, + "schoolSemesterId" bigint NOT NULL +); + +-- +-- Class SchooldayEvent as table schoolday_event +-- +CREATE TABLE "schoolday_event" ( + "id" bigserial PRIMARY KEY, + "eventId" text NOT NULL, + "eventType" text NOT NULL, + "eventReason" text NOT NULL, + "createdBy" text NOT NULL, + "processed" boolean NOT NULL, + "processedBy" text, + "processedAt" timestamp without time zone, + "documentId" bigint, + "processedDocumentId" bigint, + "schooldayId" bigint NOT NULL, + "pupilId" bigint NOT NULL +); + +-- +-- Class Subject as table subject +-- +CREATE TABLE "subject" ( + "id" bigserial PRIMARY KEY, + "publicId" text NOT NULL, + "name" text NOT NULL, + "description" text, + "color" text, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modifiedBy" text NOT NULL +); + +-- +-- Class SupportCategory as table support_category +-- +CREATE TABLE "support_category" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "categoryId" bigint NOT NULL, + "parentCategory" bigint +); + +-- +-- Class SupportGoal as table support_category_goal +-- +CREATE TABLE "support_category_goal" ( + "id" bigserial PRIMARY KEY, + "goalId" text NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "score" bigint NOT NULL, + "achievedAt" timestamp without time zone, + "description" text NOT NULL, + "strategies" text NOT NULL, + "pupilId" bigint NOT NULL, + "supportCategoryId" bigint NOT NULL, + "_learningSupportPlanSupportgoalsLearningSupportPlanId" bigint, + "_supportCategoryCategorygoalsSupportCategoryId" bigint, + "_pupilDataSupportgoalsPupilDataId" bigint +); + +-- +-- Class SupportCategoryStatus as table support_category_status +-- +CREATE TABLE "support_category_status" ( + "id" bigserial PRIMARY KEY, + "score" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "comment" text NOT NULL, + "pupilId" bigint NOT NULL, + "supportCategoryId" bigint NOT NULL, + "learningSupportPlanId" bigint NOT NULL, + "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId" bigint, + "_supportCategoryCategorystatuesSupportCategoryId" bigint, + "_pupilDataSupportcategorystatusesPupilDataId" bigint +); + +-- +-- Class SupportGoalCheck as table support_goal_check +-- +CREATE TABLE "support_goal_check" ( + "id" bigserial PRIMARY KEY, + "checkId" text NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "score" bigint NOT NULL, + "comment" text NOT NULL, + "supportGoalId" bigint NOT NULL, + "_supportCategoryGoalGoalchecksSupportCategoryGoalId" bigint +); + +-- +-- Class SupportLevel as table support_level +-- +CREATE TABLE "support_level" ( + "id" bigserial PRIMARY KEY, + "level" bigint NOT NULL, + "comment" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "createdBy" text NOT NULL, + "pupilId" bigint NOT NULL +); + +-- +-- Class Timetable as table timetable +-- +CREATE TABLE "timetable" ( + "id" bigserial PRIMARY KEY, + "active" boolean NOT NULL, + "startsAt" timestamp without time zone NOT NULL, + "endsAt" timestamp without time zone, + "name" text NOT NULL, + "schoolSemesterId" bigint NOT NULL, + "createdBy" text NOT NULL, + "createdAt" timestamp without time zone NOT NULL, + "modified" json +); + +-- Indexes +CREATE INDEX "timetable_school_semester_idx" ON "timetable" USING btree ("schoolSemesterId"); + +-- +-- Class TimetableSlot as table timetable_slot +-- +CREATE TABLE "timetable_slot" ( + "id" bigserial PRIMARY KEY, + "day" text NOT NULL, + "startTime" text NOT NULL, + "endTime" text NOT NULL, + "timetableId" bigint NOT NULL +); + +-- +-- Class User as table user +-- +CREATE TABLE "user" ( + "id" bigserial PRIMARY KEY, + "userInfoId" bigint NOT NULL, + "role" text NOT NULL, + "matrixUserId" text, + "timeUnits" bigint NOT NULL, + "reliefTimeUnits" bigint NOT NULL, + "pupilsAuth" json, + "schooldayEventsProcessingTeam" text, + "credit" bigint NOT NULL, + "userFlags" json NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "user_info_id_unique_idx" ON "user" USING btree ("userInfoId"); + +-- +-- Class UserDevice as table user_device +-- +CREATE TABLE "user_device" ( + "id" bigserial PRIMARY KEY, + "userInfoId" bigint NOT NULL, + "deviceId" text NOT NULL, + "deviceName" text NOT NULL, + "lastLogin" timestamp without time zone NOT NULL, + "isActive" boolean NOT NULL, + "authId" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "auth_key_user_device_idx" ON "user_device" USING btree ("authId"); + +-- +-- Class Workbook as table workbook +-- +CREATE TABLE "workbook" ( + "id" bigserial PRIMARY KEY, + "isbn" bigint NOT NULL, + "name" text NOT NULL, + "subject" text, + "level" text, + "amount" bigint, + "imageUrl" text NOT NULL +); + +-- +-- Class CloudStorageEntry as table serverpod_cloud_storage +-- +CREATE TABLE "serverpod_cloud_storage" ( + "id" bigserial PRIMARY KEY, + "storageId" text NOT NULL, + "path" text NOT NULL, + "addedTime" timestamp without time zone NOT NULL, + "expiration" timestamp without time zone, + "byteData" bytea NOT NULL, + "verified" boolean NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_cloud_storage_path_idx" ON "serverpod_cloud_storage" USING btree ("storageId", "path"); +CREATE INDEX "serverpod_cloud_storage_expiration" ON "serverpod_cloud_storage" USING btree ("expiration"); + +-- +-- Class CloudStorageDirectUploadEntry as table serverpod_cloud_storage_direct_upload +-- +CREATE TABLE "serverpod_cloud_storage_direct_upload" ( + "id" bigserial PRIMARY KEY, + "storageId" text NOT NULL, + "path" text NOT NULL, + "expiration" timestamp without time zone NOT NULL, + "authKey" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_cloud_storage_direct_upload_storage_path" ON "serverpod_cloud_storage_direct_upload" USING btree ("storageId", "path"); + +-- +-- Class FutureCallEntry as table serverpod_future_call +-- +CREATE TABLE "serverpod_future_call" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "time" timestamp without time zone NOT NULL, + "serializedObject" text, + "serverId" text NOT NULL, + "identifier" text +); + +-- Indexes +CREATE INDEX "serverpod_future_call_time_idx" ON "serverpod_future_call" USING btree ("time"); +CREATE INDEX "serverpod_future_call_serverId_idx" ON "serverpod_future_call" USING btree ("serverId"); +CREATE INDEX "serverpod_future_call_identifier_idx" ON "serverpod_future_call" USING btree ("identifier"); + +-- +-- Class ServerHealthConnectionInfo as table serverpod_health_connection_info +-- +CREATE TABLE "serverpod_health_connection_info" ( + "id" bigserial PRIMARY KEY, + "serverId" text NOT NULL, + "timestamp" timestamp without time zone NOT NULL, + "active" bigint NOT NULL, + "closing" bigint NOT NULL, + "idle" bigint NOT NULL, + "granularity" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_health_connection_info_timestamp_idx" ON "serverpod_health_connection_info" USING btree ("timestamp", "serverId", "granularity"); + +-- +-- Class ServerHealthMetric as table serverpod_health_metric +-- +CREATE TABLE "serverpod_health_metric" ( + "id" bigserial PRIMARY KEY, + "name" text NOT NULL, + "serverId" text NOT NULL, + "timestamp" timestamp without time zone NOT NULL, + "isHealthy" boolean NOT NULL, + "value" double precision NOT NULL, + "granularity" bigint NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_health_metric_timestamp_idx" ON "serverpod_health_metric" USING btree ("timestamp", "serverId", "name", "granularity"); + +-- +-- Class LogEntry as table serverpod_log +-- +CREATE TABLE "serverpod_log" ( + "id" bigserial PRIMARY KEY, + "sessionLogId" bigint NOT NULL, + "messageId" bigint, + "reference" text, + "serverId" text NOT NULL, + "time" timestamp without time zone NOT NULL, + "logLevel" bigint NOT NULL, + "message" text NOT NULL, + "error" text, + "stackTrace" text, + "order" bigint NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_log_sessionLogId_idx" ON "serverpod_log" USING btree ("sessionLogId"); + +-- +-- Class MessageLogEntry as table serverpod_message_log +-- +CREATE TABLE "serverpod_message_log" ( + "id" bigserial PRIMARY KEY, + "sessionLogId" bigint NOT NULL, + "serverId" text NOT NULL, + "messageId" bigint NOT NULL, + "endpoint" text NOT NULL, + "messageName" text NOT NULL, + "duration" double precision NOT NULL, + "error" text, + "stackTrace" text, + "slow" boolean NOT NULL, + "order" bigint NOT NULL +); + +-- +-- Class MethodInfo as table serverpod_method +-- +CREATE TABLE "serverpod_method" ( + "id" bigserial PRIMARY KEY, + "endpoint" text NOT NULL, + "method" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_method_endpoint_method_idx" ON "serverpod_method" USING btree ("endpoint", "method"); + +-- +-- Class DatabaseMigrationVersion as table serverpod_migrations +-- +CREATE TABLE "serverpod_migrations" ( + "id" bigserial PRIMARY KEY, + "module" text NOT NULL, + "version" text NOT NULL, + "timestamp" timestamp without time zone +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_migrations_ids" ON "serverpod_migrations" USING btree ("module"); + +-- +-- Class QueryLogEntry as table serverpod_query_log +-- +CREATE TABLE "serverpod_query_log" ( + "id" bigserial PRIMARY KEY, + "serverId" text NOT NULL, + "sessionLogId" bigint NOT NULL, + "messageId" bigint, + "query" text NOT NULL, + "duration" double precision NOT NULL, + "numRows" bigint, + "error" text, + "stackTrace" text, + "slow" boolean NOT NULL, + "order" bigint NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_query_log_sessionLogId_idx" ON "serverpod_query_log" USING btree ("sessionLogId"); + +-- +-- Class ReadWriteTestEntry as table serverpod_readwrite_test +-- +CREATE TABLE "serverpod_readwrite_test" ( + "id" bigserial PRIMARY KEY, + "number" bigint NOT NULL +); + +-- +-- Class RuntimeSettings as table serverpod_runtime_settings +-- +CREATE TABLE "serverpod_runtime_settings" ( + "id" bigserial PRIMARY KEY, + "logSettings" json NOT NULL, + "logSettingsOverrides" json NOT NULL, + "logServiceCalls" boolean NOT NULL, + "logMalformedCalls" boolean NOT NULL +); + +-- +-- Class SessionLogEntry as table serverpod_session_log +-- +CREATE TABLE "serverpod_session_log" ( + "id" bigserial PRIMARY KEY, + "serverId" text NOT NULL, + "time" timestamp without time zone NOT NULL, + "module" text, + "endpoint" text, + "method" text, + "duration" double precision, + "numQueries" bigint, + "slow" boolean, + "error" text, + "stackTrace" text, + "authenticatedUserId" bigint, + "isOpen" boolean, + "touched" timestamp without time zone NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_session_log_serverid_idx" ON "serverpod_session_log" USING btree ("serverId"); +CREATE INDEX "serverpod_session_log_touched_idx" ON "serverpod_session_log" USING btree ("touched"); +CREATE INDEX "serverpod_session_log_isopen_idx" ON "serverpod_session_log" USING btree ("isOpen"); + +-- +-- Class AuthKey as table serverpod_auth_key +-- +CREATE TABLE "serverpod_auth_key" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "hash" text NOT NULL, + "scopeNames" json NOT NULL, + "method" text NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_auth_key_userId_idx" ON "serverpod_auth_key" USING btree ("userId"); + +-- +-- Class EmailAuth as table serverpod_email_auth +-- +CREATE TABLE "serverpod_email_auth" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "email" text NOT NULL, + "hash" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_email_auth_email" ON "serverpod_email_auth" USING btree ("email"); + +-- +-- Class EmailCreateAccountRequest as table serverpod_email_create_request +-- +CREATE TABLE "serverpod_email_create_request" ( + "id" bigserial PRIMARY KEY, + "userName" text NOT NULL, + "email" text NOT NULL, + "hash" text NOT NULL, + "verificationCode" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_email_auth_create_account_request_idx" ON "serverpod_email_create_request" USING btree ("email"); + +-- +-- Class EmailFailedSignIn as table serverpod_email_failed_sign_in +-- +CREATE TABLE "serverpod_email_failed_sign_in" ( + "id" bigserial PRIMARY KEY, + "email" text NOT NULL, + "time" timestamp without time zone NOT NULL, + "ipAddress" text NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_email_failed_sign_in_email_idx" ON "serverpod_email_failed_sign_in" USING btree ("email"); +CREATE INDEX "serverpod_email_failed_sign_in_time_idx" ON "serverpod_email_failed_sign_in" USING btree ("time"); + +-- +-- Class EmailReset as table serverpod_email_reset +-- +CREATE TABLE "serverpod_email_reset" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "verificationCode" text NOT NULL, + "expiration" timestamp without time zone NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_email_reset_verification_idx" ON "serverpod_email_reset" USING btree ("verificationCode"); + +-- +-- Class GoogleRefreshToken as table serverpod_google_refresh_token +-- +CREATE TABLE "serverpod_google_refresh_token" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "refreshToken" text NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_google_refresh_token_userId_idx" ON "serverpod_google_refresh_token" USING btree ("userId"); + +-- +-- Class UserImage as table serverpod_user_image +-- +CREATE TABLE "serverpod_user_image" ( + "id" bigserial PRIMARY KEY, + "userId" bigint NOT NULL, + "version" bigint NOT NULL, + "url" text NOT NULL +); + +-- Indexes +CREATE INDEX "serverpod_user_image_user_id" ON "serverpod_user_image" USING btree ("userId", "version"); + +-- +-- Class UserInfo as table serverpod_user_info +-- +CREATE TABLE "serverpod_user_info" ( + "id" bigserial PRIMARY KEY, + "userIdentifier" text NOT NULL, + "userName" text, + "fullName" text, + "email" text, + "created" timestamp without time zone NOT NULL, + "imageUrl" text, + "scopeNames" json NOT NULL, + "blocked" boolean NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX "serverpod_user_info_user_identifier" ON "serverpod_user_info" USING btree ("userIdentifier"); +CREATE INDEX "serverpod_user_info_email" ON "serverpod_user_info" USING btree ("email"); + +-- +-- Foreign relations for "book_tagging" table +-- +ALTER TABLE ONLY "book_tagging" + ADD CONSTRAINT "book_tagging_fk_0" + FOREIGN KEY("bookId") + REFERENCES "book"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "book_tagging" + ADD CONSTRAINT "book_tagging_fk_1" + FOREIGN KEY("bookTagId") + REFERENCES "book_tag"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "competence_check" table +-- +ALTER TABLE ONLY "competence_check" + ADD CONSTRAINT "competence_check_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_check" + ADD CONSTRAINT "competence_check_fk_1" + FOREIGN KEY("competenceId") + REFERENCES "competence"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "competence_goal" table +-- +ALTER TABLE ONLY "competence_goal" + ADD CONSTRAINT "competence_goal_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_goal" + ADD CONSTRAINT "competence_goal_fk_1" + FOREIGN KEY("competenceId") + REFERENCES "competence"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "competence_report" table +-- +ALTER TABLE ONLY "competence_report" + ADD CONSTRAINT "competence_report_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_report" + ADD CONSTRAINT "competence_report_fk_1" + FOREIGN KEY("schoolSemesterId") + REFERENCES "school_semester"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "competence_report_check" table +-- +ALTER TABLE ONLY "competence_report_check" + ADD CONSTRAINT "competence_report_check_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_report_check" + ADD CONSTRAINT "competence_report_check_fk_1" + FOREIGN KEY("competenceId") + REFERENCES "competence_report_item"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "competence_report_check" + ADD CONSTRAINT "competence_report_check_fk_2" + FOREIGN KEY("competenceReportId") + REFERENCES "competence_report"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "credit_transaction" table +-- +ALTER TABLE ONLY "credit_transaction" + ADD CONSTRAINT "credit_transaction_fk_0" + FOREIGN KEY("_pupilDataCredittransactionsPupilDataId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "hub_document" table +-- +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_0" + FOREIGN KEY("_pupilBookLendingPupilbooklendingfilesPupilBookLendingId") + REFERENCES "pupil_book_lending"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_1" + FOREIGN KEY("_competenceCheckDocumentsCompetenceCheckId") + REFERENCES "competence_check"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_2" + FOREIGN KEY("_competenceGoalDocumentsCompetenceGoalId") + REFERENCES "competence_goal"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_3" + FOREIGN KEY("_supportCategoryStatusDocumentsSupportCategoryStatusId") + REFERENCES "support_category_status"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_4" + FOREIGN KEY("_supportGoalCheckDocumentsSupportGoalCheckId") + REFERENCES "support_goal_check"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_5" + FOREIGN KEY("_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId") + REFERENCES "pre_school_medical"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "hub_document" + ADD CONSTRAINT "hub_document_fk_6" + FOREIGN KEY("_preSchoolTestPreschooltestdocumentsPreSchoolTestId") + REFERENCES "pre_school_test"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "learning_support_plan" table +-- +ALTER TABLE ONLY "learning_support_plan" + ADD CONSTRAINT "learning_support_plan_fk_0" + FOREIGN KEY("learningSupportLevelId") + REFERENCES "support_level"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "learning_support_plan" + ADD CONSTRAINT "learning_support_plan_fk_1" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "learning_support_plan" + ADD CONSTRAINT "learning_support_plan_fk_2" + FOREIGN KEY("schoolSemesterId") + REFERENCES "school_semester"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson" table +-- +ALTER TABLE ONLY "lesson" + ADD CONSTRAINT "lesson_fk_0" + FOREIGN KEY("subjectId") + REFERENCES "subject"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson_attendance" table +-- +ALTER TABLE ONLY "lesson_attendance" + ADD CONSTRAINT "lesson_attendance_fk_0" + FOREIGN KEY("lessonId") + REFERENCES "lesson"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "lesson_attendance" + ADD CONSTRAINT "lesson_attendance_fk_1" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson_group" table +-- +ALTER TABLE ONLY "lesson_group" + ADD CONSTRAINT "lesson_group_fk_0" + FOREIGN KEY("timetableId") + REFERENCES "timetable"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson_group_pupil" table +-- +ALTER TABLE ONLY "lesson_group_pupil" + ADD CONSTRAINT "lesson_group_pupil_fk_0" + FOREIGN KEY("lessonGroupId") + REFERENCES "lesson_group"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "lesson_group_pupil" + ADD CONSTRAINT "lesson_group_pupil_fk_1" + FOREIGN KEY("pupilDataId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "lesson_teacher" table +-- +ALTER TABLE ONLY "lesson_teacher" + ADD CONSTRAINT "lesson_teacher_fk_0" + FOREIGN KEY("userId") + REFERENCES "user"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "lesson_teacher" + ADD CONSTRAINT "lesson_teacher_fk_1" + FOREIGN KEY("scheduledLessonId") + REFERENCES "lesson"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "library_book" table +-- +ALTER TABLE ONLY "library_book" + ADD CONSTRAINT "library_book_fk_0" + FOREIGN KEY("bookId") + REFERENCES "book"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "library_book" + ADD CONSTRAINT "library_book_fk_1" + FOREIGN KEY("locationId") + REFERENCES "library_book_location"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "missed_class" table +-- +ALTER TABLE ONLY "missed_class" + ADD CONSTRAINT "missed_class_fk_0" + FOREIGN KEY("schooldayId") + REFERENCES "schoolday"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "missed_class" + ADD CONSTRAINT "missed_class_fk_1" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_authorization" table +-- +ALTER TABLE ONLY "pupil_authorization" + ADD CONSTRAINT "pupil_authorization_fk_0" + FOREIGN KEY("fileId") + REFERENCES "hub_document"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_authorization" + ADD CONSTRAINT "pupil_authorization_fk_1" + FOREIGN KEY("authorizationId") + REFERENCES "authorization"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_authorization" + ADD CONSTRAINT "pupil_authorization_fk_2" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_book_lending" table +-- +ALTER TABLE ONLY "pupil_book_lending" + ADD CONSTRAINT "pupil_book_lending_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_book_lending" + ADD CONSTRAINT "pupil_book_lending_fk_1" + FOREIGN KEY("libraryBookId") + REFERENCES "library_book"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_data" table +-- +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_0" + FOREIGN KEY("preSchoolMedicalId") + REFERENCES "pre_school_medical"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_1" + FOREIGN KEY("kindergardenId") + REFERENCES "kindergarden"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_2" + FOREIGN KEY("preSchoolTestId") + REFERENCES "pre_school_test"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_3" + FOREIGN KEY("avatarId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_4" + FOREIGN KEY("avatarAuthId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_5" + FOREIGN KEY("publicMediaAuthDocumentId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_data" + ADD CONSTRAINT "pupil_data_fk_6" + FOREIGN KEY("_kindergardenPupilsKindergardenId") + REFERENCES "kindergarden"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_list_entry" table +-- +ALTER TABLE ONLY "pupil_list_entry" + ADD CONSTRAINT "pupil_list_entry_fk_0" + FOREIGN KEY("schoolListId") + REFERENCES "school_list"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_list_entry" + ADD CONSTRAINT "pupil_list_entry_fk_1" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "pupil_workbook" table +-- +ALTER TABLE ONLY "pupil_workbook" + ADD CONSTRAINT "pupil_workbook_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "pupil_workbook" + ADD CONSTRAINT "pupil_workbook_fk_1" + FOREIGN KEY("workbookId") + REFERENCES "workbook"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "scheduled_lesson" table +-- +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_0" + FOREIGN KEY("subjectId") + REFERENCES "subject"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_1" + FOREIGN KEY("scheduledAtId") + REFERENCES "timetable_slot"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_2" + FOREIGN KEY("timetableId") + REFERENCES "timetable"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_3" + FOREIGN KEY("roomId") + REFERENCES "room"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_4" + FOREIGN KEY("lessonGroupId") + REFERENCES "lesson_group"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson" + ADD CONSTRAINT "scheduled_lesson_fk_5" + FOREIGN KEY("_roomScheduledlessonsRoomId") + REFERENCES "room"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "scheduled_lesson_teacher" table +-- +ALTER TABLE ONLY "scheduled_lesson_teacher" + ADD CONSTRAINT "scheduled_lesson_teacher_fk_0" + FOREIGN KEY("userId") + REFERENCES "user"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "scheduled_lesson_teacher" + ADD CONSTRAINT "scheduled_lesson_teacher_fk_1" + FOREIGN KEY("scheduledLessonId") + REFERENCES "scheduled_lesson"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "school_data" table +-- +ALTER TABLE ONLY "school_data" + ADD CONSTRAINT "school_data_fk_0" + FOREIGN KEY("logoId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "school_data" + ADD CONSTRAINT "school_data_fk_1" + FOREIGN KEY("officialSealId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "schoolday" table +-- +ALTER TABLE ONLY "schoolday" + ADD CONSTRAINT "schoolday_fk_0" + FOREIGN KEY("schoolSemesterId") + REFERENCES "school_semester"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "schoolday_event" table +-- +ALTER TABLE ONLY "schoolday_event" + ADD CONSTRAINT "schoolday_event_fk_0" + FOREIGN KEY("documentId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "schoolday_event" + ADD CONSTRAINT "schoolday_event_fk_1" + FOREIGN KEY("processedDocumentId") + REFERENCES "hub_document"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "schoolday_event" + ADD CONSTRAINT "schoolday_event_fk_2" + FOREIGN KEY("schooldayId") + REFERENCES "schoolday"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "schoolday_event" + ADD CONSTRAINT "schoolday_event_fk_3" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "support_category_goal" table +-- +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_1" + FOREIGN KEY("supportCategoryId") + REFERENCES "support_category"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_2" + FOREIGN KEY("_learningSupportPlanSupportgoalsLearningSupportPlanId") + REFERENCES "learning_support_plan"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_3" + FOREIGN KEY("_supportCategoryCategorygoalsSupportCategoryId") + REFERENCES "support_category"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_goal" + ADD CONSTRAINT "support_category_goal_fk_4" + FOREIGN KEY("_pupilDataSupportgoalsPupilDataId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "support_category_status" table +-- +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_1" + FOREIGN KEY("supportCategoryId") + REFERENCES "support_category"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_2" + FOREIGN KEY("learningSupportPlanId") + REFERENCES "learning_support_plan"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_3" + FOREIGN KEY("_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId") + REFERENCES "learning_support_plan"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_4" + FOREIGN KEY("_supportCategoryCategorystatuesSupportCategoryId") + REFERENCES "support_category"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_category_status" + ADD CONSTRAINT "support_category_status_fk_5" + FOREIGN KEY("_pupilDataSupportcategorystatusesPupilDataId") + REFERENCES "pupil_data"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "support_goal_check" table +-- +ALTER TABLE ONLY "support_goal_check" + ADD CONSTRAINT "support_goal_check_fk_0" + FOREIGN KEY("supportGoalId") + REFERENCES "support_category_goal"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; +ALTER TABLE ONLY "support_goal_check" + ADD CONSTRAINT "support_goal_check_fk_1" + FOREIGN KEY("_supportCategoryGoalGoalchecksSupportCategoryGoalId") + REFERENCES "support_category_goal"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "support_level" table +-- +ALTER TABLE ONLY "support_level" + ADD CONSTRAINT "support_level_fk_0" + FOREIGN KEY("pupilId") + REFERENCES "pupil_data"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "timetable" table +-- +ALTER TABLE ONLY "timetable" + ADD CONSTRAINT "timetable_fk_0" + FOREIGN KEY("schoolSemesterId") + REFERENCES "school_semester"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "timetable_slot" table +-- +ALTER TABLE ONLY "timetable_slot" + ADD CONSTRAINT "timetable_slot_fk_0" + FOREIGN KEY("timetableId") + REFERENCES "timetable"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "user" table +-- +ALTER TABLE ONLY "user" + ADD CONSTRAINT "user_fk_0" + FOREIGN KEY("userInfoId") + REFERENCES "serverpod_user_info"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "user_device" table +-- +ALTER TABLE ONLY "user_device" + ADD CONSTRAINT "user_device_fk_0" + FOREIGN KEY("userInfoId") + REFERENCES "serverpod_user_info"("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION; +ALTER TABLE ONLY "user_device" + ADD CONSTRAINT "user_device_fk_1" + FOREIGN KEY("authId") + REFERENCES "serverpod_auth_key"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "serverpod_log" table +-- +ALTER TABLE ONLY "serverpod_log" + ADD CONSTRAINT "serverpod_log_fk_0" + FOREIGN KEY("sessionLogId") + REFERENCES "serverpod_session_log"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "serverpod_message_log" table +-- +ALTER TABLE ONLY "serverpod_message_log" + ADD CONSTRAINT "serverpod_message_log_fk_0" + FOREIGN KEY("sessionLogId") + REFERENCES "serverpod_session_log"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + +-- +-- Foreign relations for "serverpod_query_log" table +-- +ALTER TABLE ONLY "serverpod_query_log" + ADD CONSTRAINT "serverpod_query_log_fk_0" + FOREIGN KEY("sessionLogId") + REFERENCES "serverpod_session_log"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION; + + +-- +-- MIGRATION VERSION FOR school_data_hub +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('school_data_hub', '20251123092602494', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20251123092602494', "timestamp" = now(); + +-- +-- MIGRATION VERSION FOR serverpod +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('serverpod', '20240516151843329', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20240516151843329', "timestamp" = now(); + +-- +-- MIGRATION VERSION FOR serverpod_auth +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('serverpod_auth', '20240520102713718', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20240520102713718', "timestamp" = now(); + + +COMMIT; diff --git a/school_data_hub_server/migrations/20251123092602494/definition_project.json b/school_data_hub_server/migrations/20251123092602494/definition_project.json new file mode 100644 index 00000000..336b9331 --- /dev/null +++ b/school_data_hub_server/migrations/20251123092602494/definition_project.json @@ -0,0 +1,4833 @@ +{ + "moduleName": "school_data_hub", + "tables": [ + { + "name": "authorization", + "dartName": "Authorization", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('authorization_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "authorization_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "book", + "dartName": "Book", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "title", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "author", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "readingLevel", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "imagePath", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "book_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "book_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "isbn" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "book_tag", + "dartName": "BookTag", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_tag_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "book_tag_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "book_tagging", + "dartName": "BookTagging", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('book_tagging_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "bookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "bookTagId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "book_tagging_fk_0", + "columns": [ + "bookId" + ], + "referenceTable": "book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "book_tagging_fk_1", + "columns": [ + "bookTagId" + ], + "referenceTable": "book_tag", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "book_tagging_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "book_tagging_index_idx", + "elements": [ + { + "type": 0, + "definition": "bookId" + }, + { + "type": 0, + "definition": "bookTagId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "competence", + "dartName": "Competence", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentCompetence", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "level", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "indicators", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "competence_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_check", + "dartName": "CompetenceCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "checkId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "valueFactor", + "columnType": 3, + "isNullable": false, + "dartType": "double" + }, + { + "name": "groupCheckId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "groupCheckName", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_check_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_check_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_goal", + "dartName": "CompetenceGoal", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_goal_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "strategies", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "score", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_goal_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_goal_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_goal_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report", + "dartName": "CompetenceReport", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "reportId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievement", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_report_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_fk_1", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_report_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report_check", + "dartName": "CompetenceReportCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "achievement", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "competenceReportId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "competence_report_check_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_check_fk_1", + "columns": [ + "competenceId" + ], + "referenceTable": "competence_report_item", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "competence_report_check_fk_2", + "columns": [ + "competenceReportId" + ], + "referenceTable": "competence_report", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "competence_report_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "competence_report_item", + "dartName": "CompetenceReportItem", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('competence_report_item_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentItem", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "level", + "columnType": 8, + "isNullable": true, + "dartType": "List?" + }, + { + "name": "order", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "competence_report_item_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "compulsory_room", + "dartName": "CompulsoryRoom", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('compulsory_room_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "roomId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:MatrixRoomType" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "compulsory_room_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "credit_transaction", + "dartName": "CreditTransaction", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('credit_transaction_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "sender", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "receiver", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "amount", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "dateTime", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "description", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "_pupilDataCredittransactionsPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "credit_transaction_fk_0", + "columns": [ + "_pupilDataCredittransactionsPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "credit_transaction_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "reciever_idx", + "elements": [ + { + "type": 0, + "definition": "receiver" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "sender_idx", + "elements": [ + { + "type": 0, + "definition": "sender" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "hub_document", + "dartName": "HubDocument", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('hub_document_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "documentId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "documentPath", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_competenceCheckDocumentsCompetenceCheckId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_competenceGoalDocumentsCompetenceGoalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryStatusDocumentsSupportCategoryStatusId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportGoalCheckDocumentsSupportGoalCheckId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_preSchoolTestPreschooltestdocumentsPreSchoolTestId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "hub_document_fk_0", + "columns": [ + "_pupilBookLendingPupilbooklendingfilesPupilBookLendingId" + ], + "referenceTable": "pupil_book_lending", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_1", + "columns": [ + "_competenceCheckDocumentsCompetenceCheckId" + ], + "referenceTable": "competence_check", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_2", + "columns": [ + "_competenceGoalDocumentsCompetenceGoalId" + ], + "referenceTable": "competence_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_3", + "columns": [ + "_supportCategoryStatusDocumentsSupportCategoryStatusId" + ], + "referenceTable": "support_category_status", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_4", + "columns": [ + "_supportGoalCheckDocumentsSupportGoalCheckId" + ], + "referenceTable": "support_goal_check", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_5", + "columns": [ + "_preSchoolMedicalPreschoolmedicalfilesPreSchoolMedicalId" + ], + "referenceTable": "pre_school_medical", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "hub_document_fk_6", + "columns": [ + "_preSchoolTestPreschooltestdocumentsPreSchoolTestId" + ], + "referenceTable": "pre_school_test", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "hub_document_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "kindergarden", + "dartName": "Kindergarden", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('kindergarden_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "phone", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "address", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "contactPerson", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "kindergarden_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "last_pupil_identities_update", + "dartName": "LastPupilIdentiesUpdate", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('last_pupil_identities_update_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "date", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "last_pupil_identities_update_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "learning_support_plan", + "dartName": "LearningSupportPlan", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('learning_support_plan_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "planId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "number", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "socialPedagogue", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "proffesionalsInvolved", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "strengthsDescription", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "problemsDescription", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "learningSupportLevelId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "learning_support_plan_fk_0", + "columns": [ + "learningSupportLevelId" + ], + "referenceTable": "support_level", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "learning_support_plan_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "learning_support_plan_fk_2", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "learning_support_plan_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson", + "dartName": "Lesson", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "subjectId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_fk_0", + "columns": [ + "subjectId" + ], + "referenceTable": "subject", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_attendance", + "dartName": "LessonAttendance", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_attendance_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_attendance_fk_0", + "columns": [ + "lessonId" + ], + "referenceTable": "lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_attendance_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_attendance_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_group", + "dartName": "LessonGroup", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_group_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "color", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_group_fk_0", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_group_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "lesson_group_pupil", + "dartName": "ScheduledLessonGroupMembership", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_group_pupil_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lessonGroupId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilDataId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_group_pupil_fk_0", + "columns": [ + "lessonGroupId" + ], + "referenceTable": "lesson_group", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_group_pupil_fk_1", + "columns": [ + "pupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_group_pupil_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "lesson_group_membership_index_idx", + "elements": [ + { + "type": 0, + "definition": "lessonGroupId" + }, + { + "type": 0, + "definition": "pupilDataId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "lesson_teacher", + "dartName": "LessonTeacher", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('lesson_teacher_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledLessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "lesson_teacher_fk_0", + "columns": [ + "userId" + ], + "referenceTable": "user", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "lesson_teacher_fk_1", + "columns": [ + "scheduledLessonId" + ], + "referenceTable": "lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "lesson_teacher_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "lesson_teacher_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "scheduledLessonId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "library_book", + "dartName": "LibraryBook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('library_book_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "libraryId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "bookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "locationId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "available", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + } + ], + "foreignKeys": [ + { + "constraintName": "library_book_fk_0", + "columns": [ + "bookId" + ], + "referenceTable": "book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "library_book_fk_1", + "columns": [ + "locationId" + ], + "referenceTable": "library_book_location", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "library_book_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "library_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "libraryId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "library_book_location", + "dartName": "LibraryBookLocation", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('library_book_location_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "location", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "library_book_location_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "location_unique_idx", + "elements": [ + { + "type": 0, + "definition": "location" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "missed_class", + "dartName": "MissedSchoolday", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('missed_class_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "missedType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:MissedType" + }, + { + "name": "unexcused", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "contacted", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:ContactedType" + }, + { + "name": "returned", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "returnedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "writtenExcuse", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "minutesLate", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "schooldayId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "missed_class_fk_0", + "columns": [ + "schooldayId" + ], + "referenceTable": "schoolday", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "missed_class_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "missed_class_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "schoolday_pupil_data_idx", + "elements": [ + { + "type": 0, + "definition": "schooldayId" + }, + { + "type": 0, + "definition": "pupilId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "pre_school_medical", + "dartName": "PreSchoolMedical", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pre_school_medical_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "preschoolMedicalStatus", + "columnType": 0, + "isNullable": true, + "dartType": "protocol:PreSchoolMedicalStatus?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "updatedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "updatedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "pre_school_medical_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pre_school_test", + "dartName": "PreSchoolTest", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pre_school_test_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "careNeedsIntensity", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "pre_school_test_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_authorization", + "dartName": "PupilAuthorization", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_authorization_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "fileId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "authorizationId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_authorization_fk_0", + "columns": [ + "fileId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_authorization_fk_1", + "columns": [ + "authorizationId" + ], + "referenceTable": "authorization", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_authorization_fk_2", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_authorization_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_book_lending", + "dartName": "PupilBookLending", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_book_lending_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "lendingId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "status", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lentAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "lentBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "returnedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "receivedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "libraryBookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_book_lending_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_book_lending_fk_1", + "columns": [ + "libraryBookId" + ], + "referenceTable": "library_book", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_book_lending_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_data", + "dartName": "PupilData", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_data_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:PupilStatus" + }, + { + "name": "internalId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "password", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "preSchoolMedicalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "kindergardenId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "kindergardenData", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:KindergardenInfo?" + }, + { + "name": "preSchoolTestId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "avatarId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "avatarAuthId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "publicMediaAuth", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:PublicMediaAuth" + }, + { + "name": "publicMediaAuthDocumentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "contact", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "communicationPupil", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:CommunicationSkills?" + }, + { + "name": "specialInformation", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "tutorInfo", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:TutorInfo?" + }, + { + "name": "afterSchoolCare", + "columnType": 8, + "isNullable": true, + "dartType": "protocol:AfterSchoolCare?" + }, + { + "name": "credit", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "creditEarned", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "schoolyearHeldBackAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "swimmer", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "_kindergardenPupilsKindergardenId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_data_fk_0", + "columns": [ + "preSchoolMedicalId" + ], + "referenceTable": "pre_school_medical", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_1", + "columns": [ + "kindergardenId" + ], + "referenceTable": "kindergarden", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_2", + "columns": [ + "preSchoolTestId" + ], + "referenceTable": "pre_school_test", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_3", + "columns": [ + "avatarId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_4", + "columns": [ + "avatarAuthId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_5", + "columns": [ + "publicMediaAuthDocumentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "pupil_data_fk_6", + "columns": [ + "_kindergardenPupilsKindergardenId" + ], + "referenceTable": "kindergarden", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "pupil_data_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "pupil_data_status_idx", + "elements": [ + { + "type": 0, + "definition": "status" + }, + { + "type": 0, + "definition": "internalId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + }, + { + "indexName": "pupil_data_internal_id_idx", + "elements": [ + { + "type": 0, + "definition": "internalId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "pupil_list_entry", + "dartName": "PupilListEntry", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_list_entry_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "status", + "columnType": 1, + "isNullable": true, + "dartType": "bool?" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "entryBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "schoolListId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_list_entry_fk_0", + "columns": [ + "schoolListId" + ], + "referenceTable": "school_list", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_list_entry_fk_1", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "pupil_list_entry_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "pupil_workbook", + "dartName": "PupilWorkbook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('pupil_workbook_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "finishedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "workbookId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "pupil_workbook_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "pupil_workbook_fk_1", + "columns": [ + "workbookId" + ], + "referenceTable": "workbook", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "pupil_workbook_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "room", + "dartName": "Classroom", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('room_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "roomCode", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "room_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "scheduled_lesson", + "dartName": "ScheduledLesson", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('scheduled_lesson_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "active", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "subjectId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledAtId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "timetableSlotOrder", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "mainTeacherId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lessonId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "roomId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "lessonGroupId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "modifiedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "recordtest", + "columnType": 8, + "isNullable": true, + "dartType": "( {int testint, String testString})?" + }, + { + "name": "_roomScheduledlessonsRoomId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "scheduled_lesson_fk_0", + "columns": [ + "subjectId" + ], + "referenceTable": "subject", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_1", + "columns": [ + "scheduledAtId" + ], + "referenceTable": "timetable_slot", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_2", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_3", + "columns": [ + "roomId" + ], + "referenceTable": "room", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_4", + "columns": [ + "lessonGroupId" + ], + "referenceTable": "lesson_group", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_fk_5", + "columns": [ + "_roomScheduledlessonsRoomId" + ], + "referenceTable": "room", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "scheduled_lesson_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "scheduled_lesson_teacher", + "dartName": "ScheduledLessonTeacher", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('scheduled_lesson_teacher_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "scheduledLessonId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "scheduled_lesson_teacher_fk_0", + "columns": [ + "userId" + ], + "referenceTable": "user", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "scheduled_lesson_teacher_fk_1", + "columns": [ + "scheduledLessonId" + ], + "referenceTable": "scheduled_lesson", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "scheduled_lesson_teacher_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "scheduled_lesson_teacher_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userId" + }, + { + "type": 0, + "definition": "scheduledLessonId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "school_data", + "dartName": "SchoolData", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_data_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "officialName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "address", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "schoolNumber", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "telephoneNumber", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "email", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "website", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "logoId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "officialSealId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "school_data_fk_0", + "columns": [ + "logoId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "school_data_fk_1", + "columns": [ + "officialSealId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "school_data_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "school_list", + "dartName": "SchoolList", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_list_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "listId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "archived", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "public", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "authorizedUsers", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "school_list_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "school_semester", + "dartName": "SchoolSemester", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('school_semester_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "schoolYear", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "isFirst", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "startDate", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "endDate", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "classConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "supportConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "reportConferenceDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "reportSignedDate", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "school_semester_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "schoolday", + "dartName": "Schoolday", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('schoolday_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "schoolday", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "schoolday_fk_0", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "schoolday_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "schoolday_event", + "dartName": "SchooldayEvent", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('schoolday_event_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "eventId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "eventType", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:SchooldayEventType" + }, + { + "name": "eventReason", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "processed", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "processedBy", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "processedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "documentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "processedDocumentId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "schooldayId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "schoolday_event_fk_0", + "columns": [ + "documentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_1", + "columns": [ + "processedDocumentId" + ], + "referenceTable": "hub_document", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_2", + "columns": [ + "schooldayId" + ], + "referenceTable": "schoolday", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "schoolday_event_fk_3", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "schoolday_event_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "subject", + "dartName": "Subject", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('subject_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "publicId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "description", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "color", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modifiedBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "subject_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category", + "dartName": "SupportCategory", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "categoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "parentCategory", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "support_category_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category_goal", + "dartName": "SupportGoal", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_goal_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "goalId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "achievedAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "description", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "strategies", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "supportCategoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_learningSupportPlanSupportgoalsLearningSupportPlanId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryCategorygoalsSupportCategoryId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_pupilDataSupportgoalsPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_category_goal_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_1", + "columns": [ + "supportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "support_category_goal_fk_2", + "columns": [ + "_learningSupportPlanSupportgoalsLearningSupportPlanId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_3", + "columns": [ + "_supportCategoryCategorygoalsSupportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_goal_fk_4", + "columns": [ + "_pupilDataSupportgoalsPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_category_goal_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_category_status", + "dartName": "SupportCategoryStatus", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_category_status_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "supportCategoryId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "learningSupportPlanId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_supportCategoryCategorystatuesSupportCategoryId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "_pupilDataSupportcategorystatusesPupilDataId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_category_status_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_1", + "columns": [ + "supportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_2", + "columns": [ + "learningSupportPlanId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_3", + "columns": [ + "_learningSupportPlanSupportcategorystatusesLearningSupporfb7bId" + ], + "referenceTable": "learning_support_plan", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_4", + "columns": [ + "_supportCategoryCategorystatuesSupportCategoryId" + ], + "referenceTable": "support_category", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "support_category_status_fk_5", + "columns": [ + "_pupilDataSupportcategorystatusesPupilDataId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_category_status_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_goal_check", + "dartName": "SupportGoalCheck", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_goal_check_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "checkId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "score", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "supportGoalId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "_supportCategoryGoalGoalchecksSupportCategoryGoalId", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + } + ], + "foreignKeys": [ + { + "constraintName": "support_goal_check_fk_0", + "columns": [ + "supportGoalId" + ], + "referenceTable": "support_category_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + }, + { + "constraintName": "support_goal_check_fk_1", + "columns": [ + "_supportCategoryGoalGoalchecksSupportCategoryGoalId" + ], + "referenceTable": "support_category_goal", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "support_goal_check_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "support_level", + "dartName": "SupportLevel", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('support_level_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "level", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "comment", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "pupilId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "support_level_fk_0", + "columns": [ + "pupilId" + ], + "referenceTable": "pupil_data", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "support_level_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "timetable", + "dartName": "Timetable", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('timetable_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "active", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "startsAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "endsAt", + "columnType": 4, + "isNullable": true, + "dartType": "DateTime?" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "schoolSemesterId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "createdBy", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "createdAt", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "modified", + "columnType": 8, + "isNullable": true, + "dartType": "List<( {String modifiedBy, DateTime modifiedAt})>?" + } + ], + "foreignKeys": [ + { + "constraintName": "timetable_fk_0", + "columns": [ + "schoolSemesterId" + ], + "referenceTable": "school_semester", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "timetable_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "timetable_school_semester_idx", + "elements": [ + { + "type": 0, + "definition": "schoolSemesterId" + } + ], + "type": "btree", + "isUnique": false, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "timetable_slot", + "dartName": "TimetableSlot", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('timetable_slot_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "day", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:Weekday" + }, + { + "name": "startTime", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "endTime", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "timetableId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "timetable_slot_fk_0", + "columns": [ + "timetableId" + ], + "referenceTable": "timetable", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + } + ], + "indexes": [ + { + "indexName": "timetable_slot_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + }, + { + "name": "user", + "dartName": "User", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('user_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userInfoId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "role", + "columnType": 0, + "isNullable": false, + "dartType": "protocol:Role" + }, + { + "name": "matrixUserId", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "timeUnits", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "reliefTimeUnits", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "pupilsAuth", + "columnType": 8, + "isNullable": true, + "dartType": "Set?" + }, + { + "name": "schooldayEventsProcessingTeam", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "credit", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "userFlags", + "columnType": 8, + "isNullable": false, + "dartType": "protocol:UserFlags" + } + ], + "foreignKeys": [ + { + "constraintName": "user_fk_0", + "columns": [ + "userInfoId" + ], + "referenceTable": "serverpod_user_info", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "user_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "user_info_id_unique_idx", + "elements": [ + { + "type": 0, + "definition": "userInfoId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "user_device", + "dartName": "UserDevice", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('user_device_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "userInfoId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "deviceId", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "deviceName", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "lastLogin", + "columnType": 4, + "isNullable": false, + "dartType": "DateTime" + }, + { + "name": "isActive", + "columnType": 1, + "isNullable": false, + "dartType": "bool" + }, + { + "name": "authId", + "columnType": 6, + "isNullable": false, + "dartType": "int" + } + ], + "foreignKeys": [ + { + "constraintName": "user_device_fk_0", + "columns": [ + "userInfoId" + ], + "referenceTable": "serverpod_user_info", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 3 + }, + { + "constraintName": "user_device_fk_1", + "columns": [ + "authId" + ], + "referenceTable": "serverpod_auth_key", + "referenceTableSchema": "public", + "referenceColumns": [ + "id" + ], + "onUpdate": 3, + "onDelete": 4 + } + ], + "indexes": [ + { + "indexName": "user_device_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + }, + { + "indexName": "auth_key_user_device_idx", + "elements": [ + { + "type": 0, + "definition": "authId" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": false + } + ], + "managed": true + }, + { + "name": "workbook", + "dartName": "Workbook", + "module": "school_data_hub", + "schema": "public", + "columns": [ + { + "name": "id", + "columnType": 6, + "isNullable": false, + "columnDefault": "nextval('workbook_id_seq'::regclass)", + "dartType": "int?" + }, + { + "name": "isbn", + "columnType": 6, + "isNullable": false, + "dartType": "int" + }, + { + "name": "name", + "columnType": 0, + "isNullable": false, + "dartType": "String" + }, + { + "name": "subject", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "level", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + }, + { + "name": "amount", + "columnType": 6, + "isNullable": true, + "dartType": "int?" + }, + { + "name": "imageUrl", + "columnType": 0, + "isNullable": false, + "dartType": "String" + } + ], + "foreignKeys": [], + "indexes": [ + { + "indexName": "workbook_pkey", + "elements": [ + { + "type": 0, + "definition": "id" + } + ], + "type": "btree", + "isUnique": true, + "isPrimary": true + } + ], + "managed": true + } + ], + "installedModules": [ + { + "module": "serverpod", + "version": "20240516151843329" + }, + { + "module": "serverpod_auth", + "version": "20240520102713718" + } + ], + "migrationApiVersion": 1 +} \ No newline at end of file diff --git a/school_data_hub_server/migrations/20251123092602494/migration.json b/school_data_hub_server/migrations/20251123092602494/migration.json new file mode 100644 index 00000000..3d250ef2 --- /dev/null +++ b/school_data_hub_server/migrations/20251123092602494/migration.json @@ -0,0 +1,28 @@ +{ + "actions": [ + { + "type": "alterTable", + "alterTable": { + "name": "user", + "schema": "public", + "addColumns": [ + { + "name": "schooldayEventsProcessingTeam", + "columnType": 0, + "isNullable": true, + "dartType": "String?" + } + ], + "deleteColumns": [], + "modifyColumns": [], + "addIndexes": [], + "deleteIndexes": [], + "addForeignKeys": [], + "deleteForeignKeys": [], + "warnings": [] + } + } + ], + "warnings": [], + "migrationApiVersion": 1 +} \ No newline at end of file diff --git a/school_data_hub_server/migrations/20251123092602494/migration.sql b/school_data_hub_server/migrations/20251123092602494/migration.sql new file mode 100644 index 00000000..5670d71f --- /dev/null +++ b/school_data_hub_server/migrations/20251123092602494/migration.sql @@ -0,0 +1,33 @@ +BEGIN; + +-- +-- ACTION ALTER TABLE +-- +ALTER TABLE "user" ADD COLUMN "schooldayEventsProcessingTeam" text; + +-- +-- MIGRATION VERSION FOR school_data_hub +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('school_data_hub', '20251123092602494', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20251123092602494', "timestamp" = now(); + +-- +-- MIGRATION VERSION FOR serverpod +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('serverpod', '20240516151843329', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20240516151843329', "timestamp" = now(); + +-- +-- MIGRATION VERSION FOR serverpod_auth +-- +INSERT INTO "serverpod_migrations" ("module", "version", "timestamp") + VALUES ('serverpod_auth', '20240520102713718', now()) + ON CONFLICT ("module") + DO UPDATE SET "version" = '20240520102713718', "timestamp" = now(); + + +COMMIT; diff --git a/school_data_hub_server/migrations/migration_registry.txt b/school_data_hub_server/migrations/migration_registry.txt index 1ad1b97e..5940708c 100644 --- a/school_data_hub_server/migrations/migration_registry.txt +++ b/school_data_hub_server/migrations/migration_registry.txt @@ -11,3 +11,5 @@ 20250831155458451 20250831205613494 20250917215220208 +20251121184735230 +20251123092602494 diff --git a/school_data_hub_server/test/integration/test_tools/serverpod_test_tools.dart b/school_data_hub_server/test/integration/test_tools/serverpod_test_tools.dart index b5af17b0..f2a68516 100644 --- a/school_data_hub_server/test/integration/test_tools/serverpod_test_tools.dart +++ b/school_data_hub_server/test/integration/test_tools/serverpod_test_tools.dart @@ -78,41 +78,43 @@ import 'package:school_data_hub_server/src/generated/_features/pupil/models/pupi as _i35; import 'package:school_data_hub_server/src/generated/_features/learning_support/models/support_level.dart' as _i36; -import 'package:school_data_hub_server/src/generated/_features/school_data/models/school_data.dart' +import 'package:school_data_hub_server/src/generated/_features/pupil/models/pupil_data/after_school_care/after_school_care.dart' as _i37; -import 'package:school_data_hub_server/src/generated/_features/school_lists/models/school_list.dart' +import 'package:school_data_hub_server/src/generated/_features/school_data/models/school_data.dart' as _i38; -import 'package:school_data_hub_server/src/generated/_features/school_lists/models/pupil_entry.dart' +import 'package:school_data_hub_server/src/generated/_features/school_lists/models/school_list.dart' as _i39; -import 'package:school_data_hub_server/src/generated/_features/schoolday/models/school_semester.dart' +import 'package:school_data_hub_server/src/generated/_features/school_lists/models/pupil_entry.dart' as _i40; -import 'package:school_data_hub_server/src/generated/_features/schoolday/models/schoolday.dart' +import 'package:school_data_hub_server/src/generated/_features/schoolday/models/school_semester.dart' as _i41; -import 'package:school_data_hub_server/src/generated/_features/schoolday_events/models/schoolday_event.dart' +import 'package:school_data_hub_server/src/generated/_features/schoolday/models/schoolday.dart' as _i42; -import 'package:school_data_hub_server/src/generated/_features/schoolday_events/models/schoolday_event_type.dart' +import 'package:school_data_hub_server/src/generated/_features/schoolday_events/models/schoolday_event.dart' as _i43; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/classroom.dart' +import 'package:school_data_hub_server/src/generated/_features/schoolday_events/models/schoolday_event_type.dart' as _i44; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/lesson/lesson_group.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/classroom.dart' as _i45; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/scheduled_lesson.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/lesson/lesson_group.dart' as _i46; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/lesson_group_membership.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/scheduled_lesson.dart' as _i47; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/subject.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/lesson_group_membership.dart' as _i48; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/timetable.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/subject.dart' as _i49; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/timetable_slot.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/timetable.dart' as _i50; -import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/weekday_enum.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/timetable_slot.dart' as _i51; -import 'package:school_data_hub_server/src/generated/_features/workbooks/models/pupil_workbook.dart' +import 'package:school_data_hub_server/src/generated/_features/timetable/models/scheduled_lesson/weekday_enum.dart' as _i52; -import 'package:school_data_hub_server/src/generated/_features/workbooks/models/workbook.dart' +import 'package:school_data_hub_server/src/generated/_features/workbooks/models/pupil_workbook.dart' as _i53; -import 'dart:typed_data' as _i54; +import 'package:school_data_hub_server/src/generated/_features/workbooks/models/workbook.dart' + as _i54; +import 'dart:typed_data' as _i55; import 'package:school_data_hub_server/src/generated/protocol.dart'; import 'package:school_data_hub_server/src/generated/endpoints.dart'; export 'package:serverpod_test/serverpod_test_public_exports.dart'; @@ -442,6 +444,9 @@ class _AdminEndpoint { required int reliefTimeUnits, required List scopeNames, required bool isTester, + String? schooldayEventsProcessingTeam, + String? matrixUserId, + int? credit, }) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -464,6 +469,9 @@ class _AdminEndpoint { 'reliefTimeUnits': reliefTimeUnits, 'scopeNames': scopeNames, 'isTester': isTester, + 'schooldayEventsProcessingTeam': schooldayEventsProcessingTeam, + 'matrixUserId': matrixUserId, + 'credit': credit, }), serializationManager: _serializationManager, ); @@ -4075,6 +4083,39 @@ class _PupilUpdateEndpoint { } }); } + + _i3.Future<_i6.PupilData> updateAfterSchoolCare( + _i1.TestSessionBuilder sessionBuilder, + int pupilId, + _i37.AfterSchoolCare afterSchoolCare, + ) async { + return _i1.callAwaitableFunctionAndHandleExceptions(() async { + var _localUniqueSession = + (sessionBuilder as _i1.InternalTestSessionBuilder).internalBuild( + endpoint: 'pupilUpdate', + method: 'updateAfterSchoolCare', + ); + try { + var _localCallContext = await _endpointDispatch.getMethodCallContext( + createSessionCallback: (_) => _localUniqueSession, + endpointPath: 'pupilUpdate', + methodName: 'updateAfterSchoolCare', + parameters: _i1.testObjectToJson({ + 'pupilId': pupilId, + 'afterSchoolCare': afterSchoolCare, + }), + serializationManager: _serializationManager, + ); + var _localReturnValue = await (_localCallContext.method.call( + _localUniqueSession, + _localCallContext.arguments, + ) as _i3.Future<_i6.PupilData>); + return _localReturnValue; + } finally { + await _localUniqueSession.close(); + } + }); + } } class _SchoolDataEndpoint { @@ -4087,9 +4128,9 @@ class _SchoolDataEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i37.SchoolData> postSchoolData( + _i3.Future<_i38.SchoolData> postSchoolData( _i1.TestSessionBuilder sessionBuilder, - _i37.SchoolData schoolData, + _i38.SchoolData schoolData, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4108,7 +4149,7 @@ class _SchoolDataEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i37.SchoolData>); + ) as _i3.Future<_i38.SchoolData>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4116,7 +4157,7 @@ class _SchoolDataEndpoint { }); } - _i3.Future<_i37.SchoolData?> getSchoolData( + _i3.Future<_i38.SchoolData?> getSchoolData( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4135,7 +4176,7 @@ class _SchoolDataEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i37.SchoolData?>); + ) as _i3.Future<_i38.SchoolData?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4154,7 +4195,7 @@ class _SchoolListEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future> fetchSchoolLists( + _i3.Future> fetchSchoolLists( _i1.TestSessionBuilder sessionBuilder, String userName, ) async { @@ -4175,7 +4216,7 @@ class _SchoolListEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4183,7 +4224,7 @@ class _SchoolListEndpoint { }); } - _i3.Future<_i38.SchoolList> postSchoolList( + _i3.Future<_i39.SchoolList> postSchoolList( _i1.TestSessionBuilder sessionBuilder, String name, String description, @@ -4214,7 +4255,7 @@ class _SchoolListEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i38.SchoolList>); + ) as _i3.Future<_i39.SchoolList>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4222,7 +4263,7 @@ class _SchoolListEndpoint { }); } - _i3.Future<_i38.SchoolList> updateSchoolList( + _i3.Future<_i39.SchoolList> updateSchoolList( _i1.TestSessionBuilder sessionBuilder, int listId, String? name, @@ -4255,7 +4296,7 @@ class _SchoolListEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i38.SchoolList>); + ) as _i3.Future<_i39.SchoolList>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4292,9 +4333,9 @@ class _SchoolListEndpoint { }); } - _i3.Future<_i39.PupilListEntry> updatePupilListEntry( + _i3.Future<_i40.PupilListEntry> updatePupilListEntry( _i1.TestSessionBuilder sessionBuilder, - _i39.PupilListEntry entry, + _i40.PupilListEntry entry, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4313,7 +4354,7 @@ class _SchoolListEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i39.PupilListEntry>); + ) as _i3.Future<_i40.PupilListEntry>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4332,7 +4373,7 @@ class _SchooldayAdminEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i40.SchoolSemester> createSchoolSemester( + _i3.Future<_i41.SchoolSemester> createSchoolSemester( _i1.TestSessionBuilder sessionBuilder, String schoolYearName, DateTime startDate, @@ -4369,7 +4410,7 @@ class _SchooldayAdminEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i40.SchoolSemester>); + ) as _i3.Future<_i41.SchoolSemester>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4377,7 +4418,7 @@ class _SchooldayAdminEndpoint { }); } - _i3.Future> getAllSchoolSemesters( + _i3.Future> getAllSchoolSemesters( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4396,7 +4437,7 @@ class _SchooldayAdminEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4404,7 +4445,7 @@ class _SchooldayAdminEndpoint { }); } - _i3.Future<_i40.SchoolSemester?> getCurrentSchoolSemester( + _i3.Future<_i41.SchoolSemester?> getCurrentSchoolSemester( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4423,7 +4464,7 @@ class _SchooldayAdminEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i40.SchoolSemester?>); + ) as _i3.Future<_i41.SchoolSemester?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4433,7 +4474,7 @@ class _SchooldayAdminEndpoint { _i3.Future updateSchoolSemester( _i1.TestSessionBuilder sessionBuilder, - _i40.SchoolSemester schoolSemester, + _i41.SchoolSemester schoolSemester, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4462,7 +4503,7 @@ class _SchooldayAdminEndpoint { _i3.Future deleteSchoolSemester( _i1.TestSessionBuilder sessionBuilder, - _i40.SchoolSemester semester, + _i41.SchoolSemester semester, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4489,7 +4530,7 @@ class _SchooldayAdminEndpoint { }); } - _i3.Future<_i41.Schoolday?> createSchoolday( + _i3.Future<_i42.Schoolday?> createSchoolday( _i1.TestSessionBuilder sessionBuilder, DateTime date, ) async { @@ -4510,7 +4551,7 @@ class _SchooldayAdminEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i41.Schoolday?>); + ) as _i3.Future<_i42.Schoolday?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4518,7 +4559,7 @@ class _SchooldayAdminEndpoint { }); } - _i3.Future> createSchooldays( + _i3.Future> createSchooldays( _i1.TestSessionBuilder sessionBuilder, List dates, ) async { @@ -4539,7 +4580,7 @@ class _SchooldayAdminEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4578,7 +4619,7 @@ class _SchooldayAdminEndpoint { _i3.Future updateSchoolday( _i1.TestSessionBuilder sessionBuilder, - _i41.Schoolday schoolday, + _i42.Schoolday schoolday, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4616,7 +4657,7 @@ class _SchooldayEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future> getSchoolSemesters( + _i3.Future> getSchoolSemesters( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4635,7 +4676,7 @@ class _SchooldayEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4643,7 +4684,7 @@ class _SchooldayEndpoint { }); } - _i3.Future> getSchooldays( + _i3.Future> getSchooldays( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4662,7 +4703,7 @@ class _SchooldayEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4681,7 +4722,7 @@ class _SchooldayEventEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future> fetchSchooldayEvents( + _i3.Future> fetchSchooldayEvents( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4700,7 +4741,7 @@ class _SchooldayEventEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4708,13 +4749,16 @@ class _SchooldayEventEndpoint { }); } - _i3.Future<_i42.SchooldayEvent> createSchooldayEvent( + _i3.Future<_i43.SchooldayEvent> createSchooldayEvent( _i1.TestSessionBuilder sessionBuilder, { required int pupilId, + required String pupilNameAndGroup, + required String dateTimeAsString, required int schooldayId, - required _i43.SchooldayEventType type, + required _i44.SchooldayEventType type, required String reason, required String createdBy, + required String tutor, }) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4729,17 +4773,20 @@ class _SchooldayEventEndpoint { methodName: 'createSchooldayEvent', parameters: _i1.testObjectToJson({ 'pupilId': pupilId, + 'pupilNameAndGroup': pupilNameAndGroup, + 'dateTimeAsString': dateTimeAsString, 'schooldayId': schooldayId, 'type': type, 'reason': reason, 'createdBy': createdBy, + 'tutor': tutor, }), serializationManager: _serializationManager, ); var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i42.SchooldayEvent>); + ) as _i3.Future<_i43.SchooldayEvent>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4747,10 +4794,14 @@ class _SchooldayEventEndpoint { }); } - _i3.Future<_i42.SchooldayEvent> updateSchooldayEvent( + _i3.Future<_i43.SchooldayEvent> updateSchooldayEvent( _i1.TestSessionBuilder sessionBuilder, - _i42.SchooldayEvent schooldayEvent, - bool changedProcessedToFalse, + _i43.SchooldayEvent schooldayEvent, + bool changedProcessedStatus, + String pupilNameAndGroup, + String tutor, + String modifiedBy, + String dateTimeAsString, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4765,14 +4816,18 @@ class _SchooldayEventEndpoint { methodName: 'updateSchooldayEvent', parameters: _i1.testObjectToJson({ 'schooldayEvent': schooldayEvent, - 'changedProcessedToFalse': changedProcessedToFalse, + 'changedProcessedStatus': changedProcessedStatus, + 'pupilNameAndGroup': pupilNameAndGroup, + 'tutor': tutor, + 'modifiedBy': modifiedBy, + 'dateTimeAsString': dateTimeAsString, }), serializationManager: _serializationManager, ); var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i42.SchooldayEvent>); + ) as _i3.Future<_i43.SchooldayEvent>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4810,7 +4865,7 @@ class _SchooldayEventEndpoint { }); } - _i3.Future<_i42.SchooldayEvent> updateSchooldayEventFile( + _i3.Future<_i43.SchooldayEvent> updateSchooldayEventFile( _i1.TestSessionBuilder sessionBuilder, int schooldayEventId, String filePath, @@ -4839,7 +4894,7 @@ class _SchooldayEventEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i42.SchooldayEvent>); + ) as _i3.Future<_i43.SchooldayEvent>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4847,7 +4902,7 @@ class _SchooldayEventEndpoint { }); } - _i3.Future<_i42.SchooldayEvent> deleteSchooldayEventFile( + _i3.Future<_i43.SchooldayEvent> deleteSchooldayEventFile( _i1.TestSessionBuilder sessionBuilder, int schooldayEventId, bool isProcessed, @@ -4872,7 +4927,7 @@ class _SchooldayEventEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i42.SchooldayEvent>); + ) as _i3.Future<_i43.SchooldayEvent>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4891,9 +4946,9 @@ class _ClassroomEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i44.Classroom> createClassroom( + _i3.Future<_i45.Classroom> createClassroom( _i1.TestSessionBuilder sessionBuilder, - _i44.Classroom classroom, + _i45.Classroom classroom, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4912,7 +4967,7 @@ class _ClassroomEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i44.Classroom>); + ) as _i3.Future<_i45.Classroom>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4920,7 +4975,7 @@ class _ClassroomEndpoint { }); } - _i3.Future> fetchClassrooms( + _i3.Future> fetchClassrooms( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -4939,7 +4994,7 @@ class _ClassroomEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4947,7 +5002,7 @@ class _ClassroomEndpoint { }); } - _i3.Future<_i44.Classroom?> fetchClassroomById( + _i3.Future<_i45.Classroom?> fetchClassroomById( _i1.TestSessionBuilder sessionBuilder, int id, ) async { @@ -4968,7 +5023,7 @@ class _ClassroomEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i44.Classroom?>); + ) as _i3.Future<_i45.Classroom?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -4976,7 +5031,7 @@ class _ClassroomEndpoint { }); } - _i3.Future<_i44.Classroom?> fetchClassroomByRoomCode( + _i3.Future<_i45.Classroom?> fetchClassroomByRoomCode( _i1.TestSessionBuilder sessionBuilder, String roomCode, ) async { @@ -4997,7 +5052,7 @@ class _ClassroomEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i44.Classroom?>); + ) as _i3.Future<_i45.Classroom?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5005,7 +5060,7 @@ class _ClassroomEndpoint { }); } - _i3.Future> fetchClassroomsByRoomName( + _i3.Future> fetchClassroomsByRoomName( _i1.TestSessionBuilder sessionBuilder, String roomName, ) async { @@ -5026,7 +5081,7 @@ class _ClassroomEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5034,9 +5089,9 @@ class _ClassroomEndpoint { }); } - _i3.Future<_i44.Classroom> updateClassroom( + _i3.Future<_i45.Classroom> updateClassroom( _i1.TestSessionBuilder sessionBuilder, - _i44.Classroom classroom, + _i45.Classroom classroom, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5055,7 +5110,7 @@ class _ClassroomEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i44.Classroom>); + ) as _i3.Future<_i45.Classroom>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5103,9 +5158,9 @@ class _LearningGroupEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i45.LessonGroup> createLessonGroup( + _i3.Future<_i46.LessonGroup> createLessonGroup( _i1.TestSessionBuilder sessionBuilder, - _i45.LessonGroup lessonGroup, + _i46.LessonGroup lessonGroup, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5124,7 +5179,7 @@ class _LearningGroupEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i45.LessonGroup>); + ) as _i3.Future<_i46.LessonGroup>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5132,7 +5187,7 @@ class _LearningGroupEndpoint { }); } - _i3.Future> fetchLessonGroups( + _i3.Future> fetchLessonGroups( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5151,7 +5206,7 @@ class _LearningGroupEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5159,7 +5214,7 @@ class _LearningGroupEndpoint { }); } - _i3.Future<_i45.LessonGroup?> fetchLessonGroupById( + _i3.Future<_i46.LessonGroup?> fetchLessonGroupById( _i1.TestSessionBuilder sessionBuilder, int id, ) async { @@ -5180,7 +5235,7 @@ class _LearningGroupEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i45.LessonGroup?>); + ) as _i3.Future<_i46.LessonGroup?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5188,7 +5243,7 @@ class _LearningGroupEndpoint { }); } - _i3.Future<_i45.LessonGroup?> fetchLessonGroupByPublicId( + _i3.Future<_i46.LessonGroup?> fetchLessonGroupByPublicId( _i1.TestSessionBuilder sessionBuilder, String publicId, ) async { @@ -5209,7 +5264,7 @@ class _LearningGroupEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i45.LessonGroup?>); + ) as _i3.Future<_i46.LessonGroup?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5217,7 +5272,7 @@ class _LearningGroupEndpoint { }); } - _i3.Future> fetchLessonGroupsByName( + _i3.Future> fetchLessonGroupsByName( _i1.TestSessionBuilder sessionBuilder, String name, ) async { @@ -5238,7 +5293,7 @@ class _LearningGroupEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5246,7 +5301,7 @@ class _LearningGroupEndpoint { }); } - _i3.Future> fetchLessonGroupsByCreator( + _i3.Future> fetchLessonGroupsByCreator( _i1.TestSessionBuilder sessionBuilder, String createdBy, ) async { @@ -5267,7 +5322,7 @@ class _LearningGroupEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5275,7 +5330,7 @@ class _LearningGroupEndpoint { }); } - _i3.Future> fetchLessonGroupsByTimetable( + _i3.Future> fetchLessonGroupsByTimetable( _i1.TestSessionBuilder sessionBuilder, int timetableId, ) async { @@ -5296,7 +5351,7 @@ class _LearningGroupEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5304,9 +5359,9 @@ class _LearningGroupEndpoint { }); } - _i3.Future<_i45.LessonGroup> updateLessonGroup( + _i3.Future<_i46.LessonGroup> updateLessonGroup( _i1.TestSessionBuilder sessionBuilder, - _i45.LessonGroup lessonGroup, + _i46.LessonGroup lessonGroup, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5325,7 +5380,7 @@ class _LearningGroupEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i45.LessonGroup>); + ) as _i3.Future<_i46.LessonGroup>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5373,9 +5428,9 @@ class _ScheduledLessonEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i46.ScheduledLesson?> createScheduledLesson( + _i3.Future<_i47.ScheduledLesson?> createScheduledLesson( _i1.TestSessionBuilder sessionBuilder, - _i46.ScheduledLesson scheduledLesson, + _i47.ScheduledLesson scheduledLesson, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5395,7 +5450,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i46.ScheduledLesson?>); + ) as _i3.Future<_i47.ScheduledLesson?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5403,7 +5458,7 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future> fetchScheduledLessons( + _i3.Future> fetchScheduledLessons( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5422,7 +5477,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5430,7 +5485,7 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future<_i46.ScheduledLesson?> fetchScheduledLessonById( + _i3.Future<_i47.ScheduledLesson?> fetchScheduledLessonById( _i1.TestSessionBuilder sessionBuilder, int id, ) async { @@ -5451,7 +5506,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i46.ScheduledLesson?>); + ) as _i3.Future<_i47.ScheduledLesson?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5459,7 +5514,7 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future> fetchScheduledLessonsByTimetable( + _i3.Future> fetchScheduledLessonsByTimetable( _i1.TestSessionBuilder sessionBuilder, int timetableId, ) async { @@ -5480,7 +5535,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5488,7 +5543,7 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future> fetchScheduledLessonsBySubject( + _i3.Future> fetchScheduledLessonsBySubject( _i1.TestSessionBuilder sessionBuilder, int subjectId, ) async { @@ -5509,7 +5564,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5517,7 +5572,7 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future> fetchScheduledLessonsByRoom( + _i3.Future> fetchScheduledLessonsByRoom( _i1.TestSessionBuilder sessionBuilder, int roomId, ) async { @@ -5538,7 +5593,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5546,7 +5601,7 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future> fetchScheduledLessonsBySlotId( + _i3.Future> fetchScheduledLessonsBySlotId( _i1.TestSessionBuilder sessionBuilder, int slotId, ) async { @@ -5567,7 +5622,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5575,7 +5630,7 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future> fetchActiveScheduledLessons( + _i3.Future> fetchActiveScheduledLessons( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5594,7 +5649,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5602,9 +5657,9 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future<_i46.ScheduledLesson?> updateScheduledLesson( + _i3.Future<_i47.ScheduledLesson?> updateScheduledLesson( _i1.TestSessionBuilder sessionBuilder, - _i46.ScheduledLesson scheduledLesson, + _i47.ScheduledLesson scheduledLesson, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5624,7 +5679,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i46.ScheduledLesson?>); + ) as _i3.Future<_i47.ScheduledLesson?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5632,7 +5687,7 @@ class _ScheduledLessonEndpoint { }); } - _i3.Future<_i46.ScheduledLesson?> deactivateScheduledLesson( + _i3.Future<_i47.ScheduledLesson?> deactivateScheduledLesson( _i1.TestSessionBuilder sessionBuilder, int id, ) async { @@ -5653,7 +5708,7 @@ class _ScheduledLessonEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i46.ScheduledLesson?>); + ) as _i3.Future<_i47.ScheduledLesson?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5701,10 +5756,10 @@ class _ScheduledLessonGroupMembershipEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i47.ScheduledLessonGroupMembership> + _i3.Future<_i48.ScheduledLessonGroupMembership> createScheduledLessonGroupMembership( _i1.TestSessionBuilder sessionBuilder, - _i47.ScheduledLessonGroupMembership membership, + _i48.ScheduledLessonGroupMembership membership, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5723,7 +5778,7 @@ class _ScheduledLessonGroupMembershipEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i47.ScheduledLessonGroupMembership>); + ) as _i3.Future<_i48.ScheduledLessonGroupMembership>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5731,7 +5786,7 @@ class _ScheduledLessonGroupMembershipEndpoint { }); } - _i3.Future> + _i3.Future> fetchScheduledLessonGroupMemberships( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { @@ -5751,7 +5806,7 @@ class _ScheduledLessonGroupMembershipEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5759,7 +5814,7 @@ class _ScheduledLessonGroupMembershipEndpoint { }); } - _i3.Future<_i47.ScheduledLessonGroupMembership?> + _i3.Future<_i48.ScheduledLessonGroupMembership?> fetchScheduledLessonGroupMembershipById( _i1.TestSessionBuilder sessionBuilder, int id, @@ -5781,7 +5836,7 @@ class _ScheduledLessonGroupMembershipEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i47.ScheduledLessonGroupMembership?>); + ) as _i3.Future<_i48.ScheduledLessonGroupMembership?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5789,7 +5844,7 @@ class _ScheduledLessonGroupMembershipEndpoint { }); } - _i3.Future> + _i3.Future> fetchMembershipsByLessonGroupId( _i1.TestSessionBuilder sessionBuilder, int lessonGroupId, @@ -5811,7 +5866,7 @@ class _ScheduledLessonGroupMembershipEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5819,7 +5874,7 @@ class _ScheduledLessonGroupMembershipEndpoint { }); } - _i3.Future> + _i3.Future> fetchMembershipsByPupilDataId( _i1.TestSessionBuilder sessionBuilder, int pupilDataId, @@ -5841,7 +5896,7 @@ class _ScheduledLessonGroupMembershipEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5849,7 +5904,7 @@ class _ScheduledLessonGroupMembershipEndpoint { }); } - _i3.Future<_i47.ScheduledLessonGroupMembership?> + _i3.Future<_i48.ScheduledLessonGroupMembership?> fetchMembershipByLessonGroupAndPupil( _i1.TestSessionBuilder sessionBuilder, int lessonGroupId, @@ -5875,7 +5930,7 @@ class _ScheduledLessonGroupMembershipEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i47.ScheduledLessonGroupMembership?>); + ) as _i3.Future<_i48.ScheduledLessonGroupMembership?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -5883,10 +5938,10 @@ class _ScheduledLessonGroupMembershipEndpoint { }); } - _i3.Future<_i47.ScheduledLessonGroupMembership> + _i3.Future<_i48.ScheduledLessonGroupMembership> updateScheduledLessonGroupMembership( _i1.TestSessionBuilder sessionBuilder, - _i47.ScheduledLessonGroupMembership membership, + _i48.ScheduledLessonGroupMembership membership, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -5905,7 +5960,7 @@ class _ScheduledLessonGroupMembershipEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i47.ScheduledLessonGroupMembership>); + ) as _i3.Future<_i48.ScheduledLessonGroupMembership>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6019,9 +6074,9 @@ class _SubjectEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i48.Subject> createSubject( + _i3.Future<_i49.Subject> createSubject( _i1.TestSessionBuilder sessionBuilder, - _i48.Subject subject, + _i49.Subject subject, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6040,7 +6095,7 @@ class _SubjectEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i48.Subject>); + ) as _i3.Future<_i49.Subject>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6048,7 +6103,7 @@ class _SubjectEndpoint { }); } - _i3.Future> fetchSubjects( + _i3.Future> fetchSubjects( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6067,7 +6122,7 @@ class _SubjectEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6075,7 +6130,7 @@ class _SubjectEndpoint { }); } - _i3.Future<_i48.Subject?> fetchSubjectById( + _i3.Future<_i49.Subject?> fetchSubjectById( _i1.TestSessionBuilder sessionBuilder, int id, ) async { @@ -6096,7 +6151,7 @@ class _SubjectEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i48.Subject?>); + ) as _i3.Future<_i49.Subject?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6104,7 +6159,7 @@ class _SubjectEndpoint { }); } - _i3.Future<_i48.Subject?> fetchSubjectByPublicId( + _i3.Future<_i49.Subject?> fetchSubjectByPublicId( _i1.TestSessionBuilder sessionBuilder, String publicId, ) async { @@ -6125,7 +6180,7 @@ class _SubjectEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i48.Subject?>); + ) as _i3.Future<_i49.Subject?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6133,7 +6188,7 @@ class _SubjectEndpoint { }); } - _i3.Future> fetchSubjectsByName( + _i3.Future> fetchSubjectsByName( _i1.TestSessionBuilder sessionBuilder, String name, ) async { @@ -6154,7 +6209,7 @@ class _SubjectEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6162,7 +6217,7 @@ class _SubjectEndpoint { }); } - _i3.Future> fetchSubjectsByCreator( + _i3.Future> fetchSubjectsByCreator( _i1.TestSessionBuilder sessionBuilder, String createdBy, ) async { @@ -6183,7 +6238,7 @@ class _SubjectEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6191,9 +6246,9 @@ class _SubjectEndpoint { }); } - _i3.Future<_i48.Subject> updateSubject( + _i3.Future<_i49.Subject> updateSubject( _i1.TestSessionBuilder sessionBuilder, - _i48.Subject subject, + _i49.Subject subject, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6212,7 +6267,7 @@ class _SubjectEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i48.Subject>); + ) as _i3.Future<_i49.Subject>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6260,9 +6315,9 @@ class _TimetableEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i49.Timetable> createTimetable( + _i3.Future<_i50.Timetable> createTimetable( _i1.TestSessionBuilder sessionBuilder, - _i49.Timetable timetable, + _i50.Timetable timetable, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6281,7 +6336,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i49.Timetable>); + ) as _i3.Future<_i50.Timetable>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6289,7 +6344,7 @@ class _TimetableEndpoint { }); } - _i3.Future> fetchTimetables( + _i3.Future> fetchTimetables( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6308,7 +6363,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6316,7 +6371,7 @@ class _TimetableEndpoint { }); } - _i3.Future<_i49.Timetable?> fetchTimetableById( + _i3.Future<_i50.Timetable?> fetchTimetableById( _i1.TestSessionBuilder sessionBuilder, int id, ) async { @@ -6337,7 +6392,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i49.Timetable?>); + ) as _i3.Future<_i50.Timetable?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6345,7 +6400,7 @@ class _TimetableEndpoint { }); } - _i3.Future<_i49.Timetable?> fetchTimetable( + _i3.Future<_i50.Timetable?> fetchTimetable( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6364,7 +6419,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i49.Timetable?>); + ) as _i3.Future<_i50.Timetable?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6372,7 +6427,7 @@ class _TimetableEndpoint { }); } - _i3.Future<_i49.Timetable?> fetchCompleteTimetableData( + _i3.Future<_i50.Timetable?> fetchCompleteTimetableData( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6391,7 +6446,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i49.Timetable?>); + ) as _i3.Future<_i50.Timetable?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6399,7 +6454,7 @@ class _TimetableEndpoint { }); } - _i3.Future> fetchActiveTimetables( + _i3.Future> fetchActiveTimetables( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6418,7 +6473,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6426,7 +6481,7 @@ class _TimetableEndpoint { }); } - _i3.Future> fetchTimetablesBySemester( + _i3.Future> fetchTimetablesBySemester( _i1.TestSessionBuilder sessionBuilder, int schoolSemesterId, ) async { @@ -6448,7 +6503,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6456,9 +6511,9 @@ class _TimetableEndpoint { }); } - _i3.Future<_i49.Timetable> updateTimetable( + _i3.Future<_i50.Timetable> updateTimetable( _i1.TestSessionBuilder sessionBuilder, - _i49.Timetable timetable, + _i50.Timetable timetable, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6477,7 +6532,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i49.Timetable>); + ) as _i3.Future<_i50.Timetable>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6485,7 +6540,7 @@ class _TimetableEndpoint { }); } - _i3.Future<_i49.Timetable> deactivateTimetable( + _i3.Future<_i50.Timetable> deactivateTimetable( _i1.TestSessionBuilder sessionBuilder, int id, ) async { @@ -6506,7 +6561,7 @@ class _TimetableEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i49.Timetable>); + ) as _i3.Future<_i50.Timetable>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6554,9 +6609,9 @@ class _TimetableSlotEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i50.TimetableSlot> createTimetableSlot( + _i3.Future<_i51.TimetableSlot> createTimetableSlot( _i1.TestSessionBuilder sessionBuilder, - _i50.TimetableSlot timetableSlot, + _i51.TimetableSlot timetableSlot, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6575,7 +6630,7 @@ class _TimetableSlotEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i50.TimetableSlot>); + ) as _i3.Future<_i51.TimetableSlot>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6583,7 +6638,7 @@ class _TimetableSlotEndpoint { }); } - _i3.Future> fetchTimetableSlots( + _i3.Future> fetchTimetableSlots( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6602,7 +6657,7 @@ class _TimetableSlotEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6610,7 +6665,7 @@ class _TimetableSlotEndpoint { }); } - _i3.Future<_i50.TimetableSlot?> fetchTimetableSlotById( + _i3.Future<_i51.TimetableSlot?> fetchTimetableSlotById( _i1.TestSessionBuilder sessionBuilder, int id, ) async { @@ -6631,7 +6686,7 @@ class _TimetableSlotEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i50.TimetableSlot?>); + ) as _i3.Future<_i51.TimetableSlot?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6639,7 +6694,7 @@ class _TimetableSlotEndpoint { }); } - _i3.Future> fetchTimetableSlotsByTimetableId( + _i3.Future> fetchTimetableSlotsByTimetableId( _i1.TestSessionBuilder sessionBuilder, int timetableId, ) async { @@ -6660,7 +6715,7 @@ class _TimetableSlotEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6668,9 +6723,9 @@ class _TimetableSlotEndpoint { }); } - _i3.Future> fetchTimetableSlotsByDay( + _i3.Future> fetchTimetableSlotsByDay( _i1.TestSessionBuilder sessionBuilder, - _i51.Weekday day, + _i52.Weekday day, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6689,7 +6744,7 @@ class _TimetableSlotEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6697,9 +6752,9 @@ class _TimetableSlotEndpoint { }); } - _i3.Future<_i50.TimetableSlot> updateTimetableSlot( + _i3.Future<_i51.TimetableSlot> updateTimetableSlot( _i1.TestSessionBuilder sessionBuilder, - _i50.TimetableSlot timetableSlot, + _i51.TimetableSlot timetableSlot, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6718,7 +6773,7 @@ class _TimetableSlotEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i50.TimetableSlot>); + ) as _i3.Future<_i51.TimetableSlot>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6891,7 +6946,7 @@ class _PupilWorkbooksEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i52.PupilWorkbook> postPupilWorkbook( + _i3.Future<_i53.PupilWorkbook> postPupilWorkbook( _i1.TestSessionBuilder sessionBuilder, int isbn, int pupilId, @@ -6918,7 +6973,7 @@ class _PupilWorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i52.PupilWorkbook>); + ) as _i3.Future<_i53.PupilWorkbook>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6926,7 +6981,7 @@ class _PupilWorkbooksEndpoint { }); } - _i3.Future> fetchPupilWorkbooks( + _i3.Future> fetchPupilWorkbooks( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -6945,7 +7000,7 @@ class _PupilWorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6953,7 +7008,7 @@ class _PupilWorkbooksEndpoint { }); } - _i3.Future> fetchPupilWorkbooksFromPupil( + _i3.Future> fetchPupilWorkbooksFromPupil( _i1.TestSessionBuilder sessionBuilder, int pupilId, ) async { @@ -6974,7 +7029,7 @@ class _PupilWorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -6982,9 +7037,9 @@ class _PupilWorkbooksEndpoint { }); } - _i3.Future<_i52.PupilWorkbook> updatePupilWorkbook( + _i3.Future<_i53.PupilWorkbook> updatePupilWorkbook( _i1.TestSessionBuilder sessionBuilder, - _i52.PupilWorkbook pupilWorkbook, + _i53.PupilWorkbook pupilWorkbook, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -7003,7 +7058,7 @@ class _PupilWorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i52.PupilWorkbook>); + ) as _i3.Future<_i53.PupilWorkbook>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -7052,9 +7107,9 @@ class _WorkbooksEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i53.Workbook> postWorkbook( + _i3.Future<_i54.Workbook> postWorkbook( _i1.TestSessionBuilder sessionBuilder, - _i53.Workbook workbook, + _i54.Workbook workbook, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -7073,7 +7128,7 @@ class _WorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i53.Workbook>); + ) as _i3.Future<_i54.Workbook>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -7081,7 +7136,7 @@ class _WorkbooksEndpoint { }); } - _i3.Future<_i53.Workbook> fetchWorkbookByIsbn( + _i3.Future<_i54.Workbook> fetchWorkbookByIsbn( _i1.TestSessionBuilder sessionBuilder, int isbn, ) async { @@ -7102,7 +7157,7 @@ class _WorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i53.Workbook>); + ) as _i3.Future<_i54.Workbook>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -7110,7 +7165,7 @@ class _WorkbooksEndpoint { }); } - _i3.Future> fetchWorkbooks( + _i3.Future> fetchWorkbooks( _i1.TestSessionBuilder sessionBuilder) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -7129,7 +7184,7 @@ class _WorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future>); + ) as _i3.Future>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -7137,9 +7192,9 @@ class _WorkbooksEndpoint { }); } - _i3.Future<_i53.Workbook> updateWorkbook( + _i3.Future<_i54.Workbook> updateWorkbook( _i1.TestSessionBuilder sessionBuilder, - _i53.Workbook workbook, + _i54.Workbook workbook, ) async { return _i1.callAwaitableFunctionAndHandleExceptions(() async { var _localUniqueSession = @@ -7158,7 +7213,7 @@ class _WorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i53.Workbook>); + ) as _i3.Future<_i54.Workbook>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -7272,7 +7327,7 @@ class _FilesEndpoint { }); } - _i3.Future<_i54.ByteData?> getImage( + _i3.Future<_i55.ByteData?> getImage( _i1.TestSessionBuilder sessionBuilder, String documentId, ) async { @@ -7293,7 +7348,7 @@ class _FilesEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i54.ByteData?>); + ) as _i3.Future<_i55.ByteData?>); return _localReturnValue; } finally { await _localUniqueSession.close(); @@ -7301,7 +7356,7 @@ class _FilesEndpoint { }); } - _i3.Future<_i54.ByteData?> getUnencryptedImage( + _i3.Future<_i55.ByteData?> getUnencryptedImage( _i1.TestSessionBuilder sessionBuilder, String path, ) async { @@ -7322,7 +7377,7 @@ class _FilesEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i54.ByteData?>); + ) as _i3.Future<_i55.ByteData?>); return _localReturnValue; } finally { await _localUniqueSession.close(); diff --git a/school_data_hub_server/uml_diagram.puml b/school_data_hub_server/uml_diagram.puml index b6f7b38f..c4b74867 100644 --- a/school_data_hub_server/uml_diagram.puml +++ b/school_data_hub_server/uml_diagram.puml @@ -957,11 +957,13 @@ class User <user>> #e2f0fb##[bold] { ## Staff model ➡️ userInfo : module:auth:UserInfo?, relation(onDelete=Cascade) role: Role + matrixUserId: String? timeUnits: int reliefTimeUnits: int ➡️ scheduledLessonsTeacher : List?, relation(name=user_scheduled_lesson) ➡️ lessonsTeacher : List?, relation(name=user_lesson) pupilsAuth: Set? + schooldayEventsProcessingTeam: String? credit: int userFlags: UserFlags indexes: diff --git a/test_data/mock_users.json b/test_data/mock_users.json new file mode 100644 index 00000000..e69de29b