diff --git a/client/src/app/domain/definitions/permission.config.ts b/client/src/app/domain/definitions/permission.config.ts index d4e77a95b0..e53f46d07d 100644 --- a/client/src/app/domain/definitions/permission.config.ts +++ b/client/src/app/domain/definitions/permission.config.ts @@ -217,7 +217,9 @@ Meeting specific information: Structure level, Group, Participant number, About }, { display_name: _(`Can see sensitive data`), - help_text: _(`Can see email, username and SSO identification of all participants.`), + help_text: _( + `Can see email, username, membership number, SSO identification and locked out state of all participants.` + ), value: Permission.userCanSeeSensitiveData }, { diff --git a/client/src/app/domain/models/meeting-users/meeting-user.ts b/client/src/app/domain/models/meeting-users/meeting-user.ts index 7a90d7de09..da6b2bf257 100644 --- a/client/src/app/domain/models/meeting-users/meeting-user.ts +++ b/client/src/app/domain/models/meeting-users/meeting-user.ts @@ -11,6 +11,7 @@ export class MeetingUser extends BaseDecimalModel { public readonly number!: string; public readonly about_me!: string; public readonly vote_weight!: number; + public readonly locked_out!: boolean; public user_id!: Id; public meeting_id!: Id; @@ -42,6 +43,7 @@ export class MeetingUser extends BaseDecimalModel { `number`, `about_me`, `vote_weight`, + `locked_out`, `user_id`, `meeting_id`, `personal_note_ids`, diff --git a/client/src/app/gateways/repositories/meeting_user/meeting-user-repository.service.ts b/client/src/app/gateways/repositories/meeting_user/meeting-user-repository.service.ts index 1ff8b96974..0a9f7135e0 100644 --- a/client/src/app/gateways/repositories/meeting_user/meeting-user-repository.service.ts +++ b/client/src/app/gateways/repositories/meeting_user/meeting-user-repository.service.ts @@ -33,7 +33,8 @@ export class MeetingUserRepositoryService extends BaseMeetingRelatedRepository = [`about_me`, `user_id`, `meeting_id`]; @@ -57,7 +58,8 @@ export class MeetingUserRepositoryService extends BaseMeetingRelatedRepository val !== undefined).length > 1 && partialPayload.meeting_id) diff --git a/client/src/app/gateways/repositories/users/user-repository.service.ts b/client/src/app/gateways/repositories/users/user-repository.service.ts index a9bb648777..30007d7706 100644 --- a/client/src/app/gateways/repositories/users/user-repository.service.ts +++ b/client/src/app/gateways/repositories/users/user-repository.service.ts @@ -26,9 +26,10 @@ export type RawUser = FullNameInformation & Identifiable & Displayable & { fqid: export type GeneralUser = ViewUser & ViewMeetingUser; /** - * Unified type name for state fields like `is_active`, `is_physical_person` and `is_present_in_meetings`. + * Unified type name for state fields like `is_active`, `is_physical_person`, `is_present_in_meetings` + * and 'locked_out'. */ -export type UserStateField = 'is_active' | 'is_present_in_meetings' | 'is_physical_person'; +export type UserStateField = 'is_active' | 'is_present_in_meetings' | 'is_physical_person' | 'locked_out'; export interface AssignMeetingsPayload { meeting_ids: Id[]; @@ -130,7 +131,7 @@ export class UserRepositoryService extends BaseRepository { const participantListFields: TypedFieldset = participantListFieldsMinimal .concat(filterableListFields) - .concat([`is_present_in_meeting_ids`, `default_password`]); + .concat([`is_present_in_meeting_ids`, `default_password`, `committee_ids`, `committee_management_ids`]); const detailFields: TypedFieldset = [`default_password`, `can_change_own_password`]; @@ -543,7 +544,8 @@ export class UserRepositoryService extends BaseRepository { `comment`, `about_me`, `number`, - `structure_level` + `structure_level`, + `locked_out` ]; if (!create) { fields.push(`member_number`); diff --git a/client/src/app/site/pages/meetings/modules/meetings-navigation/components/meetings-navigation-wrapper/meetings-navigation-wrapper.component.html b/client/src/app/site/pages/meetings/modules/meetings-navigation/components/meetings-navigation-wrapper/meetings-navigation-wrapper.component.html index 2f4a7f8fd3..cf8fdc79be 100644 --- a/client/src/app/site/pages/meetings/modules/meetings-navigation/components/meetings-navigation-wrapper/meetings-navigation-wrapper.component.html +++ b/client/src/app/site/pages/meetings/modules/meetings-navigation/components/meetings-navigation-wrapper/meetings-navigation-wrapper.component.html @@ -1,4 +1,4 @@ - + +
+ + + {{ 'locked out' | translate }} + +
+ @if (isLockedOutAndCanManage) { +

+ It is not allowed to set the permisson UserCanManage to a locked out user. Please unset the + lockout before adding a group with UserCanManage. +

+ }
{{ 'Comment' | translate }} } @if (isAllowed('seeName')) {
- {{ (user?.isPresentInMeeting() ? 'Is present' : 'Is not present') | translate }} {{ user!.isPresentInMeeting() ? 'check_box' : 'check_box_outline_blank' }} + {{ (user?.isPresentInMeeting() ? 'Is present' : 'Is not present') | translate }} +
+
+ + @if (isAllowed('seeSensitiveData')) { + + {{ user!.isLockedOutOfMeeting() ? 'visibility_off' : 'check_box_outline_blank' }} + + + {{ (user?.isLockedOutOfMeeting() ? 'Is locked out' : 'Is not locked out') | translate }} + + }
} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-view/participant-detail-view.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-view/participant-detail-view.component.scss index 1bec7c36a9..dc527d7884 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-view/participant-detail-view.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-view/participant-detail-view.component.scss @@ -45,3 +45,7 @@ os-user-detail-view h2 { :host ::ng-deep .detail-view { @include detail-view-appearance; } + +.padding-left-12 { + padding-left: 12px; +} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-view/participant-detail-view.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-view/participant-detail-view.component.ts index a961f6f871..d03d4fedec 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-view/participant-detail-view.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-view/participant-detail-view.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild } from '@angular/core'; import { Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { marker as _ } from '@colsen1991/ngx-translate-extract-marker'; @@ -7,6 +7,7 @@ import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { Id } from 'src/app/domain/definitions/key-types'; import { OML } from 'src/app/domain/definitions/organization-permission'; import { Permission } from 'src/app/domain/definitions/permission'; +import { UserDetailViewComponent } from 'src/app/site/modules/user-components'; import { BaseMeetingComponent } from 'src/app/site/pages/meetings/base/base-meeting.component'; import { ViewGroup } from 'src/app/site/pages/meetings/pages/participants'; import { @@ -35,6 +36,9 @@ import { ViewStructureLevel } from '../../../structure-levels/view-models'; changeDetection: ChangeDetectionStrategy.OnPush }) export class ParticipantDetailViewComponent extends BaseMeetingComponent { + @ViewChild(UserDetailViewComponent) + private userDetailView; + public participantSubscriptionConfig = getParticipantMinimalSubscriptionConfig(this.activeMeetingId); public readonly additionalFormControls = { @@ -46,7 +50,8 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent { group_ids: [``], vote_delegations_from_ids: [``], vote_delegated_to_id: [``], - is_present: [``] + is_present: [``], + locked_out: [``] }; public sortFn = (groupA: ViewGroup, groupB: ViewGroup): number => groupA.weight - groupB.weight; @@ -64,6 +69,9 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent { if (controlName === `is_present` && user) { return user.isPresentInMeeting(); } + if (controlName === `locked_out` && user) { + return user.isLockedOutOfMeeting(); + } const value = user?.[controlName as keyof ViewUser] || null; return typeof value === `function` ? value.bind(user)(this.activeMeetingId!) : value; }; @@ -157,6 +165,22 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent { return this._isVoteDelegationEnabled; } + public get saveButtonEnabled(): boolean { + return this.isFormValid && !this.isLockedOutAndCanManage; + } + + public get isLockedOutAndCanManage(): boolean { + const lockedOutHelper = this.personalInfoFormValue?.locked_out ?? this.user?.is_locked_out; + return lockedOutHelper && this.checkSelectedGroupsCanManage(); + } + + public get lockoutCheckboxDisabled(): boolean { + const other = this.user?.id !== this.operator.operatorId; + const notChanged = (this.personalInfoFormValue?.locked_out ?? null) === null; + const isLockedOut = this.user?.is_locked_out; + return notChanged && !isLockedOut && (this.checkSelectedGroupsCanManage() || !other); + } + private _userId: Id | undefined = undefined; // Not initialized private _isVoteWeightEnabled = false; private _isVoteDelegationEnabled = false; @@ -338,6 +362,17 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent { } } + public updateByValueChange(event: any): void { + this.personalInfoFormValue = event; + if (this.userDetailView.personalInfoForm.get(`locked_out`).disabled !== this.lockoutCheckboxDisabled) { + if (this.lockoutCheckboxDisabled) { + this.userDetailView.personalInfoForm.get(`locked_out`).disable(); + } else { + this.userDetailView.personalInfoForm.get(`locked_out`).enable(); + } + } + } + private async createUser(): Promise { const partialUser = { ...this.personalInfoFormValue }; @@ -424,4 +459,8 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent { private goToAllUsers(): void { this.router.navigate([this.activeMeetingId, `participants`]); } + + private checkSelectedGroupsCanManage(): boolean { + return this.usersGroups.some(group => group.hasPermission(Permission.userCanManage)); + } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.html index 6431f187d8..9bfb9bccf1 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.html @@ -136,7 +136,7 @@

{{ 'Suitable accounts found' | translate }}

[patchFormValueFn]="patchFormValueFn" [shouldEnableFormControlFn]="shouldEnableFormControlFn" [user]="user" - (changeEvent)="personalInfoFormValue = $event" + (changeEvent)="updateByValueChange($event)" (errorEvent)="formErrors = $event" (validEvent)="isFormValid = $event" > @@ -153,7 +153,21 @@

{{ 'Meeting specific information' | translate }}

> {{ 'present' | translate }} + + + {{ 'locked out' | translate }} +
+ @if (isLockedOutAndCanManage) { +

+ It is not allowed to set the permisson UserCanManage to a locked out user. + Please unset the lockout before adding a group with UserCanManage. +

+ }
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.ts index af42444849..978cbe3add 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.ts @@ -5,6 +5,7 @@ import { MatStepper } from '@angular/material/stepper'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, distinctUntilChanged, Observable } from 'rxjs'; import { Id } from 'src/app/domain/definitions/key-types'; +import { Permission } from 'src/app/domain/definitions/permission'; import { User } from 'src/app/domain/models/users/user'; import { SearchUsersPresenterService } from 'src/app/gateways/presenter/search-users-presenter.service'; import { createEmailValidator } from 'src/app/infrastructure/utils/validators/email'; @@ -51,7 +52,8 @@ export class ParticipantCreateWizardComponent extends BaseMeetingComponent imple group_ids: [``], vote_delegations_from_ids: [``], vote_delegated_to_id: [``], - is_present: [``] + is_present: [``], + locked_out: [``] }; public get randomPasswordFn(): (() => string) | null { @@ -319,4 +321,32 @@ export class ParticipantCreateWizardComponent extends BaseMeetingComponent imple } } } + + public get isLockedOutAndCanManage(): boolean { + const lockedOutHelper = this.personalInfoFormValue?.locked_out ?? this.user?.is_locked_out; + return lockedOutHelper && this.checkSelectedGroupsCanManage(); + } + + public get lockoutCheckboxDisabled(): boolean { + const notChanged = (this.personalInfoFormValue?.locked_out ?? null) === null; + const isLockedOut = this.user?.is_locked_out; + return notChanged && !isLockedOut && this.checkSelectedGroupsCanManage(); + } + + public updateByValueChange(event: any): void { + this.personalInfoFormValue = event; + if (this.detailView.personalInfoForm.get(`locked_out`).disabled !== this.lockoutCheckboxDisabled) { + if (this.lockoutCheckboxDisabled) { + this.detailView.personalInfoForm.get(`locked_out`).disable(); + } else { + this.detailView.personalInfoForm.get(`locked_out`).enable(); + } + } + } + + private checkSelectedGroupsCanManage(): boolean { + return (this.detailView.personalInfoForm.get(`group_ids`).value ?? []) + .map((id: Id): ViewGroup => this.groupRepo.getViewModel(id)) + .some(group => group.hasPermission(Permission.userCanManage)); + } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/definitions/index.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/definitions/index.ts index 663ee73b74..5959a0f947 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/definitions/index.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/definitions/index.ts @@ -8,6 +8,7 @@ export const participantHeadersAndVerboseNames: { [key in keyof GeneralUser]?: a number: `Participant number`, vote_weight: `Vote weight`, is_present: `Is present`, + locked_out: `Locked out`, comment: `Comment` }; @@ -28,6 +29,7 @@ export const participantColumns: (keyof GeneralUser)[] = [ `is_active`, `is_physical_person`, `is_present`, + `locked_out`, `saml_id`, `comment` ]; diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html index 502a32db89..fc4fe590ce 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html @@ -89,9 +89,6 @@

{{ 'Participants' | translate }}

@if (!user.is_physical_person) { account_balance } - @if (!user.is_active && canSeeSensitiveData) { - block - } @if (user.hasSamlId && canSeeSensitiveData) { fingerprint } @@ -179,17 +176,34 @@

{{ 'Participants' | translate }}

-
- - {{ 'Present' | translate }} - +
+
+ + {{ 'Present' | translate }} + + @if (user.is_locked_out) { +
+
+ visibility_off + {{ 'Locked out' | translate }} +
+
+ } + @if (!user.is_active && canSeeSensitiveData) { +
+
+ block + {{ 'Inactive' | translate }} +
+
+ } +
-
+ visibility_off + {{ 'Locked out' | translate }} + + +
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.scss index c0a2b7acaf..8045d3b939 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.scss @@ -32,3 +32,12 @@ margin-left: 1em; z-index: 3; } + +.icon-part { + display: flex; + + span { + margin-top: 1px; + margin-left: 12px; + } +} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts index 1ce9a0679e..4a5cd48a3c 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts @@ -210,6 +210,10 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent group.hasPermission(Permission.userCanManage)) + ); + } + /** * This function opens the dialog, * where the user can quick change the groups, @@ -440,6 +463,10 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent { + await this.setStateSelected(`locked_out`); + } + /** * Sets the user present * @@ -454,6 +481,22 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent { + const isAllowed = this.canUpdate; + if (isAllowed) { + const title = this.isUserLockedOut(viewUser) + ? this.translate.instant(`Do you really want to include the participant back into the meeting?`) + : this.translate.instant(`Do you really want to lock this participant out of the meeting?`); + const content = viewUser.full_name; + if (await this.prompt.open(title, content)) { + this.repo.setState(`locked_out`, !this.isUserLockedOut(viewUser), viewUser); + } + } + } + public async sendInvitationEmail(viewUser: ViewUser): Promise { const title = this.translate.instant(`Are you sure you want to send an invitation email?`); const content = viewUser.full_name; @@ -498,6 +541,9 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent this.filterForLockedOut(user)); + if (filteredRows.length > 0) { + await this.repo.setState(field, value, ...filteredRows); + } + const missing = this.selectedRows.length - filteredRows.length; + if (missing > 0) { + this.matSnackBar.open( + this.translate + .instant(`%num% participants could not be locked out, because of missing permissions.`) + .replace(`%num%`, missing), + this.translate.instant(`Ok`), + { duration: 3000 } + ); + } } else { await this.repo.setState(field, value, ...this.selectedRows); } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/services/participant-list-filter/participant-list-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/services/participant-list-filter/participant-list-filter.service.ts index 350616c014..5d999850ed 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/services/participant-list-filter/participant-list-filter.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/services/participant-list-filter/participant-list-filter.service.ts @@ -73,6 +73,14 @@ export class ParticipantListFilterService extends BaseMeetingFilterListService { { property: [`first_name`, `last_name`], label: _(`Given name`) }, { property: [`last_name`, `first_name`], label: _(`Surname`) }, { property: `is_present_in_meeting_ids`, label: _(`Presence`) }, + { property: `is_locked_out`, label: _(`Locked Out`) }, { property: `member_number`, label: _(`Membership number`) }, { property: `is_active`, label: _(`Is active`) }, { property: `is_physical_person`, label: _(`Is a natural person`) }, @@ -83,6 +84,10 @@ export class ParticipantListSortService extends BaseSortListService { { property: `last_login`, shouldHideFn: () => !this.operator.hasPerms(Permission.userCanUpdate) + }, + { + property: `is_locked_out`, + shouldHideFn: () => !this.operator.hasPerms(Permission.userCanSeeSensitiveData) } ]; } diff --git a/client/src/app/site/pages/meetings/pages/participants/services/common/participant-controller.service/participant-controller.service.ts b/client/src/app/site/pages/meetings/pages/participants/services/common/participant-controller.service/participant-controller.service.ts index 25040c381c..8ef5d05c3c 100644 --- a/client/src/app/site/pages/meetings/pages/participants/services/common/participant-controller.service/participant-controller.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/services/common/participant-controller.service/participant-controller.service.ts @@ -50,7 +50,8 @@ export const MEETING_RELATED_FORM_CONTROLS = [ `group_ids`, `vote_delegations_from_ids`, `vote_delegated_to_id`, - `is_present` + `is_present`, + `locked_out` ]; @Injectable({ @@ -244,6 +245,16 @@ export class ParticipantControllerService extends BaseMeetingControllerService { + this.repo.preventAlterationOnDemoUsers(users); + const payload: any[] = users.map(user => ({ + id: user.id, + meeting_id: this.activeMeetingId, + locked_out: locked_out + })); + return this.actions.create({ action: UserAction.UPDATE, data: payload }); + } + public async removeUsersFromMeeting( users: ViewUser[], meeting: ViewMeeting = this.activeMeeting @@ -347,7 +358,8 @@ export class ParticipantControllerService extends BaseMeetingControllerService /* implements Searchable */ { return this.isPresentInMeeting(); } + public get is_locked_out(): boolean { + return this.isLockedOutOfMeeting(); + } + public get hasMemberNumber(): boolean { return !!this.member_number; } @@ -134,14 +138,32 @@ export class ViewUser extends BaseViewModel /* implements Searchable */ { return this.is_present_in_meeting_ids?.includes(meetingId) || false; } + /** + * @param meetingId The meeting id. If not provided, tha active meeting id is used. + * If there is no active meeting, an error will be thrown. + * @returns if the user is locked out of the given meeting + */ + public isLockedOutOfMeeting(meetingId?: Id): boolean { + return this.getMeetingUser(meetingId || this.getEnsuredActiveMeetingId())?.locked_out || false; + } + public get hasMultipleMeetings(): boolean { + if (this.meeting_users?.length) { + return this.meeting_users.filter(mu => !mu.locked_out).length !== 1; + } + return this.meeting_ids?.length !== 1; // In case of `0` it should not return `true` } public get onlyMeeting(): Id { - const meetingAmount = this.meeting_ids?.length || 0; + let meetingIds = this.meeting_ids || []; + if (this.meeting_users?.length) { + meetingIds = this.ensuredMeetingIds; + } + + const meetingAmount = meetingIds.length; if (meetingAmount === 1) { - return this.meeting_ids[0]; + return meetingIds[0]; } else if (meetingAmount > 1) { throw new Error(`User has multiple meetings`); } else if (meetingAmount === 0) { @@ -155,7 +177,11 @@ export class ViewUser extends BaseViewModel /* implements Searchable */ { * Returns all meetings that the user actually has group memberships for. */ public get ensuredMeetingIds(): number[] { - return this.meeting_users.filter(mUser => mUser.group_ids?.length).map(mUser => mUser.meeting_id) || []; + return ( + this.meeting_users + .filter(mUser => mUser.group_ids?.length && !mUser.locked_out) + .map(mUser => mUser.meeting_id) || [] + ); } public hasVoteRightFromOthers(meetingId?: Id): boolean { diff --git a/client/src/app/site/services/openslides-router.service.ts b/client/src/app/site/services/openslides-router.service.ts index 0edb80c623..d862ef8490 100644 --- a/client/src/app/site/services/openslides-router.service.ts +++ b/client/src/app/site/services/openslides-router.service.ts @@ -76,6 +76,11 @@ export class OpenSlidesRouterService { .subscribe(event => this._currentParamMap.next(event)); activeMeetingIdService.meetingIdChanged.subscribe(event => console.log(`has meeting changed?`, event)); + this.operator.operatorUpdated.subscribe(() => { + if (!this.operator.knowsMultipleMeetings && !this.activeMeetingIdService.meetingId) { + this.checkCurrentRouteGuards(); + } + }); this.operator.permissionsObservable .pipe( filter(v => !!v), diff --git a/client/src/app/site/services/operator.service.ts b/client/src/app/site/services/operator.service.ts index c08370be67..86e50c095d 100644 --- a/client/src/app/site/services/operator.service.ts +++ b/client/src/app/site/services/operator.service.ts @@ -364,7 +364,7 @@ export class OperatorService { } public isInMeeting(meetingId: Id): boolean { - return this.user.meeting_ids?.includes(meetingId) || false; + return this.user.ensuredMeetingIds?.includes(meetingId) || false; } private updateUser(user: ViewUser): void { diff --git a/client/src/app/site/services/reroute.service.ts b/client/src/app/site/services/reroute.service.ts index 4808f9a2c4..3dc274c9f7 100644 --- a/client/src/app/site/services/reroute.service.ts +++ b/client/src/app/site/services/reroute.service.ts @@ -38,7 +38,7 @@ export class RerouteService { const fallbackRoute = this.fallbackRoutesService.getFallbackRoute(); if (fallbackRoute && (this.operator.user || fallbackMeetingId)) { return this.router.createUrlTree([ - fallbackMeetingId ?? this.operator.user.meeting_ids[0], + fallbackMeetingId ?? this.operator.user.ensuredMeetingIds[0], fallbackRoute ]); } diff --git a/client/src/meta b/client/src/meta index 84bb58e141..29f2e62f61 160000 --- a/client/src/meta +++ b/client/src/meta @@ -1 +1 @@ -Subproject commit 84bb58e141e088f9fd0678214d5b51d040d53034 +Subproject commit 29f2e62f615168a3592aae342e15f3445118ee8e