Pull request #364: RED-3950
Merge in RED/ui from RED-3950 to master * commit '2caa66a3d306fe9092ae8e738e9ed552d274e125': RED-3950: File & dossier delete/restore permissions RED-3950: Trash module & improvements
This commit is contained in:
commit
1c9302b2aa
@ -9,10 +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 { TrashGuard } from '@guards/trash.guard';
|
||||
import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -63,6 +64,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: [
|
||||
|
||||
@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
14
apps/red-ui/src/app/guards/trash.guard.ts
Normal file
14
apps/red-ui/src/app/guards/trash.guard.ts
Normal file
@ -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<boolean> {
|
||||
await firstValueFrom(this._trashService.loadAll());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -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 '@red/domain';
|
||||
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({
|
||||
|
||||
@ -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],
|
||||
})
|
||||
|
||||
@ -69,6 +69,7 @@
|
||||
</div>
|
||||
<iqser-icon-button
|
||||
(action)="newDossier()"
|
||||
*ngIf="permissionsService.canCreateDossier()"
|
||||
[label]="'dashboard.empty-template.new-dossier' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:plus"
|
||||
|
||||
@ -4,6 +4,7 @@ import { IconButtonTypes } from '@iqser/common-ui';
|
||||
import { DossiersDialogService } from '../../../dossier/shared/services/dossiers-dialog.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-template-stats [stats]',
|
||||
@ -20,6 +21,7 @@ export class TemplateStatsComponent {
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _translateService: TranslateService,
|
||||
readonly translateChartService: TranslateChartService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
) {}
|
||||
|
||||
newDossier(): void {
|
||||
|
||||
@ -183,7 +183,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
);
|
||||
this.#canAssignToSelf = this.#canMoveToSameState && this._permissionsService.canAssignToSelf(this.selectedFiles, this.dossier);
|
||||
|
||||
this.#canDelete = this._permissionsService.canDeleteFile(this.selectedFiles, this.dossier);
|
||||
this.#canDelete = this._permissionsService.canSoftDeleteFile(this.selectedFiles, this.dossier);
|
||||
|
||||
this.#canReanalyse = this._permissionsService.canReanalyseFile(this.selectedFiles, this.dossier);
|
||||
|
||||
|
||||
@ -78,23 +78,23 @@
|
||||
<div class="dialog-actions">
|
||||
<iqser-icon-button
|
||||
(action)="deleteDossier()"
|
||||
*ngIf="permissionsService.canDeleteDossier(dossier)"
|
||||
*ngIf="permissionsService.canSoftDeleteDossier(dossier)"
|
||||
[dialogElement]="true"
|
||||
[label]="'dossier-listing.delete.action' | translate"
|
||||
[type]="iconButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
id="deleteDossier"
|
||||
iqserHelpMode="edit_dossier_delete_dossier"
|
||||
[dialogElement]="true"
|
||||
></iqser-icon-button>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="archiveDossier()"
|
||||
*ngIf="permissionsService.canArchiveDossier(dossier)"
|
||||
[dialogElement]="true"
|
||||
[label]="'dossier-listing.archive.action' | translate"
|
||||
[type]="iconButtonTypes.dark"
|
||||
icon="red:archive"
|
||||
iqserHelpMode="edit_dossier_archive_dossier"
|
||||
[dialogElement]="true"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<Dossier>[] {
|
||||
@ -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',
|
||||
|
||||
@ -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"
|
||||
></iqser-table>
|
||||
|
||||
@ -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<Dossier> 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<Dossier> im
|
||||
constructor(
|
||||
router: Router,
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _configService: ConfigService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
|
||||
@ -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<Dossier> {
|
||||
interface PartialDossier extends Partial<IDossier> {
|
||||
readonly dossierName: string;
|
||||
readonly dossierTemplateId: string;
|
||||
readonly dueDate?: string;
|
||||
|
||||
@ -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<DialogType> {
|
||||
protected readonly _config: DialogConfig<DialogType> = {
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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<TrashItem> implements OnInit {
|
||||
export class TrashScreenComponent extends ListingComponent<TrashItem> {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly tableHeaderLabel = _('trash.table-header.title');
|
||||
readonly canRestoreSelected$ = this._canRestoreSelected$;
|
||||
@ -46,8 +51,7 @@ export class TrashScreenComponent extends ListingComponent<TrashItem> 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);
|
||||
|
||||
@ -59,50 +63,27 @@ export class TrashScreenComponent extends ListingComponent<TrashItem> implements
|
||||
|
||||
private get _canRestoreSelected$(): Observable<boolean> {
|
||||
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<boolean> {
|
||||
return this.listingService.selectedEntities$.pipe(
|
||||
map(entities => entities.length && !entities.find(dossier => !dossier.canHardDelete)),
|
||||
map(entities => entities.length && !entities.find(dossier => !dossier.hasHardDeleteRights)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
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<void> {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._trashService.restore(items));
|
||||
items.forEach(item => this.entitiesService.remove(item.id));
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,7 @@
|
||||
<div class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="restore.emit([item])"
|
||||
*ngIf="item.canRestore"
|
||||
*ngIf="item.canRestore && item.hasRestoreRights"
|
||||
[tooltip]="'trash.action.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
@ -48,7 +48,7 @@
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete.emit([item])"
|
||||
*ngIf="item.canHardDelete"
|
||||
*ngIf="item.hasHardDeleteRights"
|
||||
[tooltip]="'trash.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
17
apps/red-ui/src/app/modules/trash/trash.module.ts
Normal file
17
apps/red-ui/src/app/modules/trash/trash.module.ts
Normal file
@ -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 {}
|
||||
@ -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<TrashItem> {
|
||||
export class TrashService extends EntitiesService<TrashItem> {
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _toaster: Toaster,
|
||||
@ -26,7 +26,7 @@ export class TrashService extends GenericService<TrashItem> {
|
||||
private readonly _dossierStatsService: DossierStatsService,
|
||||
private readonly _filesService: FilesService,
|
||||
) {
|
||||
super(_injector, '');
|
||||
super(_injector, null, '');
|
||||
}
|
||||
|
||||
deleteDossier(dossier: Dossier): Observable<unknown> {
|
||||
@ -46,7 +46,7 @@ export class TrashService extends GenericService<TrashItem> {
|
||||
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<TrashItem> {
|
||||
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<TrashDossier[]> {
|
||||
@ -65,8 +65,8 @@ export class TrashService extends GenericService<TrashItem> {
|
||||
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,14 +83,18 @@ export class TrashService extends GenericService<TrashItem> {
|
||||
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),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
all(): Observable<TrashItem[]> {
|
||||
return forkJoin([this.getDossiers(), this.getFiles()]).pipe(map(result => flatMap<TrashItem>(result)));
|
||||
loadAll(): Observable<TrashItem[]> {
|
||||
return forkJoin([this.getDossiers(), this.getFiles()]).pipe(
|
||||
map(result => flatMap<TrashItem>(result)),
|
||||
tap(items => this.setEntities(items)),
|
||||
);
|
||||
}
|
||||
|
||||
#executeAction(items: TrashItem[], dossiersFn: (...args) => Observable<unknown>, filesFn: (...args) => Observable<unknown>) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<IDossier> {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -23,9 +23,10 @@ export class TrashFile extends TrashItem implements Partial<IFile> {
|
||||
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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user