From 69a2eb275097229cf9920893b578acd1672c9a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 11 May 2022 20:03:15 +0300 Subject: [PATCH 1/2] RED-3950: Trash module & improvements --- apps/red-ui/src/app/app-routing.module.ts | 11 ++++ .../base-screen/base-screen.component.ts | 2 +- apps/red-ui/src/app/guards/trash.guard.ts | 14 ++++++ .../app/modules/admin/admin-routing.module.ts | 13 ----- .../src/app/modules/admin/admin.module.ts | 14 ++---- .../trash/services/trash-dialog.service.ts | 50 +++++++++++++++++++ .../trash-screen}/trash-screen.component.html | 0 .../trash-screen}/trash-screen.component.scss | 0 .../trash-screen}/trash-screen.component.ts | 49 ++++++------------ .../trash-table-item.component.html | 0 .../trash-table-item.component.scss | 0 .../trash-table-item.component.ts | 0 .../src/app/modules/trash/trash.module.ts | 17 +++++++ .../services/entity-services/trash.service.ts | 19 ++++--- 14 files changed, 123 insertions(+), 66 deletions(-) create mode 100644 apps/red-ui/src/app/guards/trash.guard.ts create mode 100644 apps/red-ui/src/app/modules/trash/services/trash-dialog.service.ts rename apps/red-ui/src/app/modules/{admin/screens/trash => trash/trash-screen}/trash-screen.component.html (100%) rename apps/red-ui/src/app/modules/{admin/screens/trash => trash/trash-screen}/trash-screen.component.scss (100%) rename apps/red-ui/src/app/modules/{admin/screens/trash => trash/trash-screen}/trash-screen.component.ts (64%) rename apps/red-ui/src/app/modules/{admin/screens/trash => trash/trash-screen}/trash-table-item/trash-table-item.component.html (100%) rename apps/red-ui/src/app/modules/{admin/screens/trash => trash/trash-screen}/trash-table-item/trash-table-item.component.scss (100%) rename apps/red-ui/src/app/modules/{admin/screens/trash => trash/trash-screen}/trash-table-item/trash-table-item.component.ts (100%) create mode 100644 apps/red-ui/src/app/modules/trash/trash.module.ts diff --git a/apps/red-ui/src/app/app-routing.module.ts b/apps/red-ui/src/app/app-routing.module.ts index 6c5b7a67a..3226143ee 100644 --- a/apps/red-ui/src/app/app-routing.module.ts +++ b/apps/red-ui/src/app/app-routing.module.ts @@ -14,6 +14,7 @@ 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'; const routes: Routes = [ { @@ -64,6 +65,16 @@ const routes: Routes = [ requiredRoles: ['RED_USER', 'RED_MANAGER'], }, }, + { + path: 'trash', + loadChildren: () => import('./modules/trash/trash.module').then(m => m.TrashModule), + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, DossiersGuard, TrashGuard], + requiredRoles: ['RED_USER', 'RED_MANAGER'], + dossiersService: ACTIVE_DOSSIERS_SERVICE, + }, + }, { path: `:${DOSSIER_TEMPLATE_ID}`, children: [ diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts index bc43783c2..be6c54f45 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts @@ -52,7 +52,7 @@ export class BaseScreenComponent { { id: 'trash', name: _('top-bar.navigation-items.my-account.children.trash'), - routerLink: '/main/admin/trash', + routerLink: '/main/trash', show: this.currentUser.isManager, }, ]; diff --git a/apps/red-ui/src/app/guards/trash.guard.ts b/apps/red-ui/src/app/guards/trash.guard.ts new file mode 100644 index 000000000..542a4ede1 --- /dev/null +++ b/apps/red-ui/src/app/guards/trash.guard.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { CanActivate } from '@angular/router'; +import { firstValueFrom } from 'rxjs'; +import { TrashService } from '@services/entity-services/trash.service'; + +@Injectable({ providedIn: 'root' }) +export class TrashGuard implements CanActivate { + constructor(private readonly _trashService: TrashService) {} + + async canActivate(): Promise { + await firstValueFrom(this._trashService.loadAll()); + return true; + } +} diff --git a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts index 2fcbb5317..5788fa076 100644 --- a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts @@ -11,7 +11,6 @@ import { DigitalSignatureScreenComponent } from './screens/digital-signature/dig import { AuditScreenComponent } from './screens/audit/audit-screen.component'; import { RouterModule, Routes } from '@angular/router'; import { DossierAttributesListingScreenComponent } from './screens/dossier-attributes-listing/dossier-attributes-listing-screen.component'; -import { TrashScreenComponent } from './screens/trash/trash-screen.component'; import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component'; import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component'; import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component'; @@ -20,8 +19,6 @@ import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard'; import { EntityExistsGuard } from '@guards/entity-exists-guard.service'; import { DossierStatesListingScreenComponent } from './screens/dossier-states-listing/dossier-states-listing-screen.component'; -import { DossiersGuard } from '@guards/dossiers.guard'; -import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens'; import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component'; import { PermissionsGuard } from '../../guards/permissions-guard'; @@ -210,16 +207,6 @@ const routes: Routes = [ requiredRoles: ['RED_ADMIN'], }, }, - { - path: 'trash', - component: TrashScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, DossiersGuard], - requiredRoles: ['RED_MANAGER'], - dossiersService: ACTIVE_DOSSIERS_SERVICE, - }, - }, ]; @NgModule({ diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index f3be32ed2..de6e99167 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -25,7 +25,6 @@ import { ResetPasswordComponent } from './dialogs/add-edit-user-dialog/reset-pas import { UserDetailsComponent } from './dialogs/add-edit-user-dialog/user-details/user-details.component'; import { AddEditDossierAttributeDialogComponent } from './dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component'; import { DossierAttributesListingScreenComponent } from './screens/dossier-attributes-listing/dossier-attributes-listing-screen.component'; -import { TrashScreenComponent } from './screens/trash/trash-screen.component'; import { AuditService } from './services/audit.service'; import { DigitalSignatureService } from './services/digital-signature.service'; import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component'; @@ -41,7 +40,6 @@ import { DossierStatesListingScreenComponent } from './screens/dossier-states-li import { AddEditDossierStateDialogComponent } from './dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component'; import { A11yModule } from '@angular/cdk/a11y'; import { ConfirmDeleteDossierStateDialogComponent } from './dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component'; -import { TrashTableItemComponent } from './screens/trash/trash-table-item/trash-table-item.component'; import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component'; import { CloneDossierTemplateDialogComponent } from './dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component'; import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component'; @@ -58,6 +56,8 @@ const dialogs = [ FileAttributesCsvImportDialogComponent, AddEditDossierAttributeDialogComponent, UploadDictionaryDialogComponent, + AddEditDossierStateDialogComponent, + ConfirmDeleteDossierStateDialogComponent, ]; const screens = [ @@ -69,7 +69,7 @@ const screens = [ UserListingScreenComponent, GeneralConfigScreenComponent, DossierAttributesListingScreenComponent, - TrashScreenComponent, + DossierStatesListingScreenComponent, ]; const components = [ @@ -90,13 +90,7 @@ const components = [ ]; @NgModule({ - declarations: [ - ...components, - DossierStatesListingScreenComponent, - AddEditDossierStateDialogComponent, - ConfirmDeleteDossierStateDialogComponent, - TrashTableItemComponent, - ], + declarations: [...components], providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, SmtpConfigService], imports: [CommonModule, SharedModule, AdminRoutingModule, SharedAdminModule, ColorPickerModule, A11yModule], }) diff --git a/apps/red-ui/src/app/modules/trash/services/trash-dialog.service.ts b/apps/red-ui/src/app/modules/trash/services/trash-dialog.service.ts new file mode 100644 index 000000000..9ef73d9d6 --- /dev/null +++ b/apps/red-ui/src/app/modules/trash/services/trash-dialog.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { + ConfirmationDialogComponent, + ConfirmationDialogInput, + DialogConfig, + DialogService, + LoadingService, + TitleColors, +} from '@iqser/common-ui'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { TrashItem } from '@red/domain'; +import { firstValueFrom } from 'rxjs'; +import { TrashService } from '@services/entity-services/trash.service'; + +type DialogType = 'confirm'; + +@Injectable() +export class TrashDialogService extends DialogService { + protected readonly _config: DialogConfig = { + confirm: { + component: ConfirmationDialogComponent, + }, + }; + + constructor( + protected readonly _dialog: MatDialog, + private readonly _loadingService: LoadingService, + private readonly _trashService: TrashService, + ) { + super(_dialog); + } + + confirmHardDelete(items: TrashItem[]): void { + const data = new ConfirmationDialogInput({ + title: _('confirmation-dialog.delete-items.title'), + titleColor: TitleColors.WARN, + question: _('confirmation-dialog.delete-items.question'), + translateParams: { + name: items[0].name, + itemsCount: items.length, + }, + }); + this.openDialog('confirm', null, data, async () => { + this._loadingService.start(); + await firstValueFrom(this._trashService.hardDelete(items)); + this._loadingService.stop(); + }); + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html b/apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.html similarity index 100% rename from apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html rename to apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.html diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.scss b/apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.scss similarity index 100% rename from apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.scss rename to apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.scss diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts b/apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.ts similarity index 64% rename from apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts rename to apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.ts index 8f77182f1..c4d97b1b4 100644 --- a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts +++ b/apps/red-ui/src/app/modules/trash/trash-screen/trash-screen.component.ts @@ -1,15 +1,13 @@ -import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, Injector } from '@angular/core'; import { CircleButtonTypes, - ConfirmationDialogInput, - DefaultListingServices, + DefaultListingServicesTmp, + EntitiesService, ListingComponent, LoadingService, SortingOrders, TableColumnConfig, - TitleColors, } from '@iqser/common-ui'; -import { AdminDialogService } from '../../services/admin-dialog.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { firstValueFrom, Observable } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; @@ -18,15 +16,22 @@ import { TrashItem } from '@red/domain'; import { TrashService } from '@services/entity-services/trash.service'; import { FilesService } from '@services/entity-services/files.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; +import { TrashDialogService } from '../services/trash-dialog.service'; @Component({ templateUrl: './trash-screen.component.html', styleUrls: ['./trash-screen.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => TrashScreenComponent) }], + providers: [ + ...DefaultListingServicesTmp, + { provide: EntitiesService, useExisting: TrashService }, + { + provide: ListingComponent, + useExisting: forwardRef(() => TrashScreenComponent), + }, + ], }) -export class TrashScreenComponent extends ListingComponent implements OnInit { +export class TrashScreenComponent extends ListingComponent { readonly circleButtonTypes = CircleButtonTypes; readonly tableHeaderLabel = _('trash.table-header.title'); readonly canRestoreSelected$ = this._canRestoreSelected$; @@ -46,8 +51,7 @@ export class TrashScreenComponent extends ListingComponent implements private readonly _activeDossiersService: ActiveDossiersService, private readonly _filesService: FilesService, readonly routerHistoryService: RouterHistoryService, - private readonly _adminDialogService: AdminDialogService, - private readonly _dossierTemplatesService: DossierTemplatesService, + private readonly _dialogService: TrashDialogService, ) { super(_injector); @@ -71,38 +75,15 @@ export class TrashScreenComponent extends ListingComponent implements ); } - async ngOnInit(): Promise { - this._loadingService.start(); - await firstValueFrom(this._dossierTemplatesService.loadAll()); - const entities: TrashItem[] = await firstValueFrom(this._trashService.all()); - this.entitiesService.setEntities(entities); - this._loadingService.stop(); - } - disabledFn = (dossier: TrashItem) => !dossier.canRestore; hardDelete(items = this.listingService.selected): void { - const data = new ConfirmationDialogInput({ - title: _('confirmation-dialog.delete-items.title'), - titleColor: TitleColors.WARN, - question: _('confirmation-dialog.delete-items.question'), - translateParams: { - name: items[0].name, - itemsCount: items.length, - }, - }); - this._adminDialogService.openDialog('confirm', null, data, async () => { - this._loadingService.start(); - await firstValueFrom(this._trashService.hardDelete(items)); - items.forEach(item => this.entitiesService.remove(item.id)); - this._loadingService.stop(); - }); + this._dialogService.confirmHardDelete(items); } async restore(items = this.listingService.selected): Promise { this._loadingService.start(); await firstValueFrom(this._trashService.restore(items)); - items.forEach(item => this.entitiesService.remove(item.id)); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/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 similarity index 100% rename from apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.html rename to apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.html diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.scss b/apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.scss similarity index 100% rename from apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.scss rename to apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.scss diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.ts b/apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.ts similarity index 100% rename from apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.ts rename to apps/red-ui/src/app/modules/trash/trash-screen/trash-table-item/trash-table-item.component.ts diff --git a/apps/red-ui/src/app/modules/trash/trash.module.ts b/apps/red-ui/src/app/modules/trash/trash.module.ts new file mode 100644 index 000000000..e145fb601 --- /dev/null +++ b/apps/red-ui/src/app/modules/trash/trash.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { TrashScreenComponent } from './trash-screen/trash-screen.component'; +import { CommonUiModule } from '@iqser/common-ui'; +import { TrashTableItemComponent } from './trash-screen/trash-table-item/trash-table-item.component'; +import { SharedModule } from '@shared/shared.module'; +import { TrashDialogService } from './services/trash-dialog.service'; + +const routes = [{ path: '', component: TrashScreenComponent }]; + +@NgModule({ + declarations: [TrashScreenComponent, TrashTableItemComponent], + imports: [CommonModule, RouterModule.forChild(routes), CommonUiModule, SharedModule], + providers: [TrashDialogService], +}) +export class TrashModule {} diff --git a/apps/red-ui/src/app/services/entity-services/trash.service.ts b/apps/red-ui/src/app/services/entity-services/trash.service.ts index 506a44315..299c2bdd1 100644 --- a/apps/red-ui/src/app/services/entity-services/trash.service.ts +++ b/apps/red-ui/src/app/services/entity-services/trash.service.ts @@ -1,7 +1,7 @@ import { Injectable, Injector } from '@angular/core'; -import { GenericService, List, mapEach, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; +import { EntitiesService, List, mapEach, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { Dossier, File, IDossier, IFile, TrashDossier, TrashFile, TrashItem } from '@red/domain'; -import { catchError, switchMap, take } from 'rxjs/operators'; +import { catchError, switchMap, take, tap } from 'rxjs/operators'; import { forkJoin, map, Observable, of } from 'rxjs'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { ConfigService } from '../config.service'; @@ -15,7 +15,7 @@ import { FilesService } from '@services/entity-services/files.service'; @Injectable({ providedIn: 'root', }) -export class TrashService extends GenericService { +export class TrashService extends EntitiesService { constructor( protected readonly _injector: Injector, private readonly _toaster: Toaster, @@ -26,7 +26,7 @@ export class TrashService extends GenericService { private readonly _dossierStatsService: DossierStatsService, private readonly _filesService: FilesService, ) { - super(_injector, ''); + super(_injector, null, ''); } deleteDossier(dossier: Dossier): Observable { @@ -46,7 +46,7 @@ export class TrashService extends GenericService { items, (dossierIds: string[]) => this._restoreDossiers(dossierIds), (dossierId: string, fileIds: string[]) => this._restoreFiles(dossierId, fileIds), - ); + ).pipe(tap(() => items.forEach(item => this.remove(item.id)))); } @Validate() @@ -55,7 +55,7 @@ export class TrashService extends GenericService { items, (dossierIds: string[]) => this._hardDeleteDossiers(dossierIds), (dossierId: string, fileIds: string[]) => this._hardDeleteFiles(dossierId, fileIds), - ); + ).pipe(tap(() => items.forEach(item => this.remove(item.id)))); } getDossiers(): Observable { @@ -89,8 +89,11 @@ export class TrashService extends GenericService { ); } - all(): Observable { - return forkJoin([this.getDossiers(), this.getFiles()]).pipe(map(result => flatMap(result))); + loadAll(): Observable { + return forkJoin([this.getDossiers(), this.getFiles()]).pipe( + map(result => flatMap(result)), + tap(items => this.setEntities(items)), + ); } #executeAction(items: TrashItem[], dossiersFn: (...args) => Observable, filesFn: (...args) => Observable) { 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 2/2] 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;