From 2caa66a3d306fe9092ae8e738e9ed552d274e125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 12 May 2022 17:19:44 +0300 Subject: [PATCH] RED-3950: File & dossier delete/restore permissions --- apps/red-ui/src/app/app-routing.module.ts | 3 +- .../template-stats.component.html | 1 + .../template-stats.component.ts | 2 + ...dossier-overview-bulk-actions.component.ts | 2 +- .../edit-dossier-general-info.component.html | 6 +-- .../file-actions/file-actions.component.ts | 2 +- .../dossiers-listing/config.service.ts | 4 +- .../dossiers-listing-screen.component.html | 2 +- .../dossiers-listing-screen.component.ts | 3 -- .../dossier-name-column.component.ts | 4 +- .../trash-screen/trash-screen.component.ts | 4 +- .../trash-table-item.component.html | 4 +- .../services/entity-services/trash.service.ts | 5 ++- .../src/app/services/permissions.service.ts | 41 ++++++++++++------- .../src/lib/trash/trash-dossier.model.ts | 9 ++-- .../src/lib/trash/trash-file.model.ts | 5 ++- libs/red-domain/src/lib/trash/trash.item.ts | 5 ++- 17 files changed, 59 insertions(+), 43 deletions(-) diff --git a/apps/red-ui/src/app/app-routing.module.ts b/apps/red-ui/src/app/app-routing.module.ts index e8a717c16..e0dbac693 100644 --- a/apps/red-ui/src/app/app-routing.module.ts +++ b/apps/red-ui/src/app/app-routing.module.ts @@ -9,12 +9,11 @@ import { DownloadsListScreenComponent } from '@components/downloads-list-screen/ import { DossiersGuard } from '@guards/dossiers.guard'; import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens'; import { FeaturesGuard } from '@guards/features-guard.service'; -import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain'; import { DossierTemplatesGuard } from '@guards/dossier-templates.guard'; import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard'; import { DashboardGuard } from '@guards/dashboard-guard.service'; -import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain'; import { TrashGuard } from '@guards/trash.guard'; +import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain'; const routes: Routes = [ { diff --git a/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.html b/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.html index 92e914f17..a49a8aa3b 100644 --- a/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.html +++ b/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.html @@ -69,6 +69,7 @@ diff --git a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts index cd2d059e1..6c9d9287d 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts @@ -398,7 +398,7 @@ export class FileActionsComponent implements OnChanges { this.canToggleAnalysis = this._permissionsService.canToggleAnalysis(this.file, this.dossier); this.showToggleAnalysis = this._permissionsService.showToggleAnalysis(this.dossier); - this.showDelete = this._permissionsService.canDeleteFile(this.file, this.dossier); + this.showDelete = this._permissionsService.canSoftDeleteFile(this.file, this.dossier); this.showOCR = this._permissionsService.canOcrFile(this.file, this.dossier); this.canReanalyse = this._permissionsService.canReanalyseFile(this.file, this.dossier); this.canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis([this.file], this.dossier); diff --git a/apps/red-ui/src/app/modules/dossiers-listing/config.service.ts b/apps/red-ui/src/app/modules/dossiers-listing/config.service.ts index f57e33fe0..ee7c06423 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/config.service.ts +++ b/apps/red-ui/src/app/modules/dossiers-listing/config.service.ts @@ -11,6 +11,7 @@ import { workloadTranslations } from '../dossier/translations/workload-translati import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; import { DossiersDialogService } from '../dossier/shared/services/dossiers-dialog.service'; +import { PermissionsService } from '@services/permissions.service'; @Injectable() export class ConfigService { @@ -21,6 +22,7 @@ export class ConfigService { private readonly _dossierStatsService: DossierStatsService, private readonly _dossierStatesMapService: DossierStatesMapService, private readonly _dialogService: DossiersDialogService, + private readonly _permissionsService: PermissionsService, ) {} get tableConfig(): TableColumnConfig[] { @@ -43,7 +45,7 @@ export class ConfigService { { label: _('dossier-listing.add-new'), action: () => this._openAddDossierDialog(dossierTemplateId), - hide: !this._currentUser.isManager, + hide: !this._permissionsService.canCreateDossier(), icon: 'iqser:plus', type: 'primary', helpModeKey: 'new_dossier_button', diff --git a/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.html b/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.html index a1051a9f8..c5b48713d 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.html @@ -16,7 +16,7 @@ [noDataButtonLabel]="'dossier-listing.no-data.action' | translate" [noDataText]="'dossier-listing.no-data.title' | translate" [noMatchText]="'dossier-listing.no-match.title' | translate" - [showNoDataButton]="currentUser.isManager" + [showNoDataButton]="permissionsService.canCreateDossier()" [tableColumnConfigs]="tableColumnConfigs" noDataIcon="red:folder" > diff --git a/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.ts b/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.ts index 8c218bb7a..952c9cd12 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.ts @@ -1,6 +1,5 @@ import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { Dossier, DOSSIER_TEMPLATE_ID } from '@red/domain'; -import { UserService } from '@services/user.service'; import { PermissionsService } from '@services/permissions.service'; import { ButtonConfig, DefaultListingServices, ListingComponent, OnAttach, TableComponent } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @@ -18,7 +17,6 @@ import { UserPreferenceService } from '@services/user-preference.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class DossiersListingScreenComponent extends ListingComponent implements OnInit, OnAttach { - readonly currentUser = this._userService.currentUser; readonly tableColumnConfigs = this._configService.tableConfig; readonly tableHeaderLabel = _('dossier-listing.table-header.title'); readonly buttonConfigs: ButtonConfig[]; @@ -33,7 +31,6 @@ export class DossiersListingScreenComponent extends ListingComponent im constructor( router: Router, protected readonly _injector: Injector, - private readonly _userService: UserService, private readonly _configService: ConfigService, readonly permissionsService: PermissionsService, private readonly _dialogService: DossiersDialogService, diff --git a/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts index 94b423232..21b8c207e 100644 --- a/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts @@ -1,12 +1,12 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { Dossier, DossierStats } from '@red/domain'; +import { DossierStats, IDossier } from '@red/domain'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { List } from '@iqser/common-ui'; import dayjs from 'dayjs'; const DUE_DATE_WARN_DAYS = 14; -interface PartialDossier extends Partial { +interface PartialDossier extends Partial { readonly dossierName: string; readonly dossierTemplateId: string; readonly dueDate?: string; diff --git a/apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.ts b/apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.ts index c4d97b1b4..5195787cd 100644 --- a/apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.ts +++ b/apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.ts @@ -63,14 +63,14 @@ export class TrashScreenComponent extends ListingComponent { private get _canRestoreSelected$(): Observable { return this.listingService.selectedEntities$.pipe( - map(entities => entities.length && !entities.find(dossier => !dossier.canRestore)), + map(entities => entities.length && !entities.find(dossier => !(dossier.canRestore && dossier.hasRestoreRights))), distinctUntilChanged(), ); } private get _canHardDeleteSelected$(): Observable { return this.listingService.selectedEntities$.pipe( - map(entities => entities.length && !entities.find(dossier => !dossier.canHardDelete)), + map(entities => entities.length && !entities.find(dossier => !dossier.hasHardDeleteRights)), distinctUntilChanged(), ); } diff --git a/apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.html b/apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.html index 39d3eacbd..a93cb9350 100644 --- a/apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.html +++ b/apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.html @@ -40,7 +40,7 @@
{ new TrashDossier( dossier, this._configService.values.DELETE_RETENTION_HOURS as number, - this._permissionsService.canHardDeleteDossier(dossier), this._permissionsService.canRestoreDossier(dossier), + this._permissionsService.canHardDeleteDossier(dossier), ), ), switchMap(dossiers => this._dossierStatsService.getFor(dossiers.map(d => d.id)).pipe(map(() => dossiers))), @@ -83,7 +83,8 @@ export class TrashService extends EntitiesService { file, dossier.dossierTemplateId, this._configService.values.DELETE_RETENTION_HOURS as number, - this._permissionsService.canDeleteFile(file, dossier), + this._permissionsService.canRestoreFile(file, dossier), + this._permissionsService.canHardDeleteFile(file, dossier), ); }), ); diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index cde00d67a..1d367ab59 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -79,13 +79,19 @@ export class PermissionsService { return file.assignee === this.#userId; } - canDeleteFile(file: File | File[], dossier: Dossier): boolean { + canSoftDeleteFile(file: File | File[], dossier: Dossier): boolean { const files = file instanceof File ? [file] : file; - return files.reduce((acc, _file) => this._canDeleteFile(_file, dossier) && acc, true); + return files.reduce((acc, _file) => this._canSoftDeleteFile(_file, dossier) && acc, true); } - canHardDeleteOrRestore(dossier: Dossier): boolean { - return dossier.isActive; + canRestoreFile(file: File | File[], dossier: Dossier): boolean { + const files = file instanceof File ? [file] : file; + return files.reduce((acc, _file) => this._canRestoreFile(_file, dossier) && acc, true); + } + + canHardDeleteFile(file: File | File[], dossier: Dossier): boolean { + const files = file instanceof File ? [file] : file; + return files.reduce((acc, _file) => this._canHardDeleteFile(_file, dossier) && acc, true); } canOcrFile(file: File | File[], dossier: Dossier): boolean { @@ -140,7 +146,7 @@ export class PermissionsService { return dossier.approverIds.indexOf(this.#userId) >= 0; } - isDossierMember(dossier: Dossier): boolean { + isDossierMember(dossier: IDossier): boolean { return dossier.memberIds.includes(this.#userId); } @@ -173,8 +179,8 @@ export class PermissionsService { return this.isApprover(dossier) && files.reduce((prev, file) => prev && file.isApproved, true); } - canDeleteDossier(dossier: IDossier): boolean { - return this.isOwner(dossier); + canSoftDeleteDossier(dossier: IDossier): boolean { + return this.isOwner(dossier) || (this.isManager() && this.isDossierMember(dossier)); } canHardDeleteDossier(dossier: IDossier): boolean { @@ -182,6 +188,10 @@ export class PermissionsService { } canRestoreDossier(dossier: IDossier): boolean { + return this.isOwner(dossier) || (this.isManager() && this.isDossierMember(dossier)); + } + + canCreateDossier(): boolean { return this.isManager(); } @@ -249,17 +259,20 @@ export class PermissionsService { return dossier.isActive && this.isFileAssignee(file) && (file.isNew || file.isUnderReview || file.isUnderApproval); } - // https://jira.iqser.com/browse/RED-2787 - private _canDeleteFile(file: File, dossier: Dossier): boolean { + private _canSoftDeleteFile(file: File, dossier: Dossier): boolean { return ( - dossier.isActive && - (file.isNew || - (file.isUnderReview && !file.assignee && this.isDossierMember(dossier)) || - (file.isUnderApproval && !file.assignee && this.isApprover(dossier)) || - (file.assignee && !file.isApproved && (this.isFileAssignee(file) || this.isOwner(dossier)))) + dossier.isActive && (this.isApprover(dossier) || this.isFileAssignee(file) || (!file.assignee && this.isDossierMember(dossier))) ); } + private _canRestoreFile(file: File, dossier: Dossier): boolean { + return this.isApprover(dossier) || this.isFileAssignee(file); + } + + private _canHardDeleteFile(file: File, dossier: Dossier): boolean { + return this.isApprover(dossier) || this.isFileAssignee(file) || (!file.assignee && this.isDossierMember(dossier)); + } + private _canReanalyseFile(file: File, dossier: Dossier): boolean { return dossier.isActive && this.isReviewerOrApprover(file, dossier) && file.analysisRequired; } diff --git a/libs/red-domain/src/lib/trash/trash-dossier.model.ts b/libs/red-domain/src/lib/trash/trash-dossier.model.ts index db1b61104..2614beec0 100644 --- a/libs/red-domain/src/lib/trash/trash-dossier.model.ts +++ b/libs/red-domain/src/lib/trash/trash-dossier.model.ts @@ -2,7 +2,7 @@ import { List } from '@iqser/common-ui'; import { IDossier } from '../dossiers'; import { TrashItem } from './trash.item'; -export class TrashDossier extends TrashItem { +export class TrashDossier extends TrashItem implements Partial { readonly type = 'dossier'; readonly icon = 'red:folder'; @@ -21,10 +21,10 @@ export class TrashDossier extends TrashItem { constructor( dossier: IDossier, protected readonly _retentionHours: number, - readonly canHardDelete: boolean, - protected readonly _hasRestoreRights: boolean, + readonly hasRestoreRights: boolean, + readonly hasHardDeleteRights: boolean, ) { - super(_retentionHours, dossier.softDeletedTime, canHardDelete); + super(_retentionHours, dossier.softDeletedTime, hasRestoreRights, hasHardDeleteRights); this.dossierId = dossier.dossierId; this.dossierTemplateId = dossier.dossierTemplateId; this.date = dossier.date; @@ -35,7 +35,6 @@ export class TrashDossier extends TrashItem { // Because of migrations, for some this is not set this.softDeletedTime = dossier.softDeletedTime || '-'; - this.canRestore = this.canRestore && this._hasRestoreRights; this.id = this.dossierId; } diff --git a/libs/red-domain/src/lib/trash/trash-file.model.ts b/libs/red-domain/src/lib/trash/trash-file.model.ts index 9e1d3b8d2..4b1431786 100644 --- a/libs/red-domain/src/lib/trash/trash-file.model.ts +++ b/libs/red-domain/src/lib/trash/trash-file.model.ts @@ -23,9 +23,10 @@ export class TrashFile extends TrashItem implements Partial { file: File, readonly dossierTemplateId: string, protected readonly _retentionHours: number, - readonly canHardDelete: boolean, + readonly hasRestoreRights: boolean, + readonly hasHardDeleteRights: boolean, ) { - super(_retentionHours, file.softDeletedTime, canHardDelete); + super(_retentionHours, file.softDeletedTime, hasRestoreRights, hasHardDeleteRights); this.fileId = file.fileId; this.dossierId = file.dossierId; this.filename = file.filename; diff --git a/libs/red-domain/src/lib/trash/trash.item.ts b/libs/red-domain/src/lib/trash/trash.item.ts index 69d374646..688144278 100644 --- a/libs/red-domain/src/lib/trash/trash.item.ts +++ b/libs/red-domain/src/lib/trash/trash.item.ts @@ -7,12 +7,13 @@ export abstract class TrashItem { abstract readonly dossierId: string; abstract readonly icon: string; readonly restoreDate: string; - canRestore: boolean; + readonly canRestore: boolean; protected constructor( protected readonly _retentionHours: number, readonly softDeletedTime: string | undefined, - readonly canHardDelete: boolean, + readonly hasRestoreRights: boolean, + readonly hasHardDeleteRights: boolean, ) { this.restoreDate = this.#restoreDate; this.canRestore = this.#canRestore;