diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue index 87d9f429075cad7a46a80dd55a82e3d01ea642c2..c2b765cfdf368764771b6ea7ff71a7bef3e8b5c2 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue @@ -69,7 +69,10 @@ <DocumentationLoader /> </template> </infinite-scrolling-date-sorted-c-r-u-d-iterator> - <absence-creation-dialog :absence-reasons="absenceReasons" /> + <absence-creation-dialog + :absence-reasons="absenceReasons" + :affected-query="lastQuery" + /> </div> </template> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue index 690b07972cd932a8d5eb0fd91609e0a662d829b0..e62c2ca4a22a314744a72a9feec45e439b9a1379 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue @@ -96,7 +96,10 @@ import permissionsMixin from "aleksis.core/mixins/permissions.js"; import mutateMixin from "aleksis.core/mixins/mutateMixin.js"; import { DateTime } from "luxon"; -import { createAbsencesForPersons } from "./absenceCreation.graphql"; +import { + clearAbsencesForPersons, + createAbsencesForPersons, +} from "./absenceCreation.graphql"; export default { name: "AbsenceCreationDialog", @@ -152,13 +155,17 @@ export default { confirm() { this.handleLoading(true); this.mutate( - createAbsencesForPersons, + this.absenceReason !== "present" + ? createAbsencesForPersons + : clearAbsencesForPersons, { persons: this.persons.map((p) => p.id), start: this.$toUTCISO(this.$parseISODate(this.startDate)), end: this.$toUTCISO(this.$parseISODate(this.endDate)), - comment: this.comment, - reason: this.absenceReason, + ...(this.absenceReason !== "present" && { comment: this.comment }), + ...(this.absenceReason !== "present" && { + reason: this.absenceReason, + }), }, (storedDocumentations, incomingStatuses) => { incomingStatuses.forEach((newStatus) => { @@ -172,7 +179,7 @@ export default { (part) => part.id === newStatus.id, ); - participationStatus.absenceReason = newStatus.absenceReason; + participationStatus.absenceReason = newStatus?.absenceReason; participationStatus.isOptimistic = newStatus.isOptimistic; }); diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue index af7d0e86bf46df86150aa65621edebae5e1eb582..601c2916a64e0f5b2ae83d13dc797519acfc9908 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue @@ -4,10 +4,9 @@ <v-row> <div aria-required="true" class="full-width"> <!-- FIXME Vue 3: clear-on-select --> - <v-autocomplete + <person-field + :gql-query="gqlQuery" :label="$t('forms.labels.persons')" - :items="allPersons" - item-text="fullName" return-object multiple chips @@ -18,7 +17,6 @@ ]) " :value="persons" - :loading="$apollo.queries.allPersons.loading" @input="$emit('persons', $event)" /> </div> @@ -69,6 +67,7 @@ <v-text-field :label="$t('forms.labels.comment')" :value="comment" + :disabled="absenceReason == 'present'" @input="$emit('comment', $event)" /> </v-row> @@ -76,6 +75,7 @@ <div aria-required="true"> <absence-reason-group-select :rules="$rules().required.build()" + allow-empty :value="absenceReason" :custom-absence-reasons="absenceReasons" @input="$emit('absence-reason', $event)" @@ -89,7 +89,8 @@ <script> import AbsenceReasonGroupSelect from "aleksis.apps.kolego/components/AbsenceReasonGroupSelect.vue"; import DateTimeField from "aleksis.core/components/generic/forms/DateTimeField.vue"; -import { periodsByDay, persons } from "./absenceCreation.graphql"; +import PersonField from "aleksis.core/components/generic/forms/PersonField.vue"; +import { gqlPersons, periodsByDay } from "./absenceCreation.graphql"; import formRulesMixin from "aleksis.core/mixins/formRulesMixin.js"; import { DateTime } from "luxon"; @@ -98,6 +99,7 @@ export default { components: { AbsenceReasonGroupSelect, DateTimeField, + PersonField, }, mixins: [formRulesMixin], emits: [ @@ -109,7 +111,6 @@ export default { "absence-reason", ], apollo: { - allPersons: persons, periodsByDay: { query: periodsByDay, result(_) { @@ -146,6 +147,7 @@ export default { }, data() { return { + gqlQuery: gqlPersons, startDT: DateTime.fromISO(this.startDate), endDT: DateTime.fromISO(this.endDate), startPeriods: false, diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceCreation.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceCreation.graphql index e164d0868c4bd378416941becb0e1e45a88eaaa9..34a22cef6431fd15450e48b40d7ce1d94553fb29 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceCreation.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceCreation.graphql @@ -1,8 +1,9 @@ # Uses core persons query -query persons { - allPersons: absenceCreationPersons { +query gqlPersons { + items: absenceCreationPersons { id fullName + shortName } } @@ -70,3 +71,23 @@ mutation createAbsencesForPersons( } } } + +mutation clearAbsencesForPersons( + $persons: [ID]! + $start: DateTime! + $end: DateTime! +) { + clearAbsencesForPersons(persons: $persons, start: $start, end: $end) { + ok + items: participationStatuses { + id + isOptimistic + relatedDocumentation { + id + } + absenceReason { + id + } + } + } +} diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 2dd3deac4fbc03f3e85b4d791170f09eaf5054e3..9b4d5d2e302c72566a4a6851826c8d2669cf2b77 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -118,7 +118,7 @@ class Documentation(CalendarEvent): return self.course.groups.all() def get_teachers_short_names(self) -> list[str]: - return [teacher.short_name or teacher.name for teacher in self.teachers.all()] + return [teacher.short_name or teacher.full_name for teacher in self.teachers.all()] def __str__(self) -> str: start_datetime = CalendarEvent.value_start_datetime(self) @@ -498,6 +498,29 @@ class ParticipationStatus(CalendarEvent): return participation_statuses + @classmethod + def clear_absence_by_datetimes( + cls, person: Person, start: datetime, end: datetime + ) -> list["ParticipationStatus"]: + participation_statuses = [] + + events = cls.get_single_events( + start, + end, + None, + {"person": person}, + with_reference_object=True, + ) + + for event in events: + participation_status = event["REFERENCE_OBJECT"] + participation_status.absence_reason = None + participation_status.base_absence = None + participation_status.save() + participation_statuses.append(participation_status) + + return participation_statuses + def fill_from_kolego(self, kolego_absence: KolegoAbsence): """Take over data from a Kolego absence.""" self.base_absence = kolego_absence diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py index a8e830da046e4679d30c9695a8b4d6ae10e0e2f1..0c75517fd62d63d0c181156c9d2529a711ff7590 100644 --- a/aleksis/apps/alsijil/schema/__init__.py +++ b/aleksis/apps/alsijil/schema/__init__.py @@ -26,6 +26,7 @@ from aleksis.core.util.core_helpers import ( from ..model_extensions import annotate_person_statistics_for_school_term from ..models import Documentation, ExtraMark, NewPersonalNote, ParticipationStatus from .absences import ( + AbsencesForPersonsClearMutation, AbsencesForPersonsCreateMutation, ) from .documentation import ( @@ -394,6 +395,7 @@ class Mutation(graphene.ObjectType): touch_documentation = TouchDocumentationMutation.Field() update_participation_statuses = ParticipationStatusBatchPatchMutation.Field() create_absences_for_persons = AbsencesForPersonsCreateMutation.Field() + clear_absences_for_persons = AbsencesForPersonsClearMutation.Field() extend_participation_statuses = ExtendParticipationStatusToAbsenceBatchMutation.Field() create_extra_marks = ExtraMarkBatchCreateMutation.Field() diff --git a/aleksis/apps/alsijil/schema/absences.py b/aleksis/apps/alsijil/schema/absences.py index eea8b075b3e1ba2a0e6011e4b7e33e6df4f09ed4..8a729f0a4ae10624c9522fb27a8a4166af78b40a 100644 --- a/aleksis/apps/alsijil/schema/absences.py +++ b/aleksis/apps/alsijil/schema/absences.py @@ -56,3 +56,44 @@ class AbsencesForPersonsCreateMutation(graphene.Mutation): return AbsencesForPersonsCreateMutation( ok=True, participation_statuses=participation_statuses ) + + +class AbsencesForPersonsClearMutation(graphene.Mutation): + class Arguments: + persons = graphene.List(graphene.ID, required=True) + start = graphene.DateTime(required=True) + end = graphene.DateTime(required=True) + + ok = graphene.Boolean() + participation_statuses = graphene.List(ParticipationStatusType) + + @classmethod + def mutate( + cls, + root, + info, + persons: list[str | int], + start: datetime.datetime, + end: datetime.datetime, + ): + participation_statuses = [] + + persons = Person.objects.filter(pk__in=persons) + + for person in persons: + if not info.context.user.has_perm("alsijil.register_absence_rule", person): + raise PermissionDenied() + + participation_statuses += ParticipationStatus.clear_absence_by_datetimes( + person=person, start=start, end=end + ) + + Absence.clear_or_extend_absences_in_timespan( + person=person, + datetime_start=start, + datetime_end=end, + ) + + return AbsencesForPersonsClearMutation( + ok=True, participation_statuses=participation_statuses + )