View archived files

This commit is contained in:
Adina Țeudan 2022-03-01 13:08:49 +02:00
parent 670e19f98e
commit 97d956c11c
37 changed files with 260 additions and 244 deletions

View File

@ -26,7 +26,7 @@ export class DossierFilesGuard implements CanActivate {
}
if (!this._filesMapService.has(dossierId)) {
await firstValueFrom(this._filesService.loadAll(dossierId));
await firstValueFrom(this._filesService.loadAll(dossierId, dossiersService.routerPath));
}
return true;
}

View File

@ -1,22 +1,25 @@
import { Injectable } from '@angular/core';
import { Injectable, Injector, ProviderToken } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
import { DossiersService } from '../services/entity-services/dossiers.service';
@Injectable({ providedIn: 'root' })
export class FilePreviewGuard implements CanActivate {
constructor(
private readonly _filesMapService: FilesMapService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _router: Router,
private readonly _injector: Injector,
) {}
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
const token: ProviderToken<DossiersService> = route.data.dossiersService;
const dossiersService: DossiersService = this._injector.get<DossiersService>(token);
const dossierId = route.paramMap.get(DOSSIER_ID);
const fileId = route.paramMap.get(FILE_ID);
const dossier = this._activeDossiersService.find(dossierId);
const dossier = dossiersService.find(dossierId);
if (!this._filesMapService.get(dossierId, fileId)) {
await this._router.navigate([dossier.routerLink]);

View File

@ -2,10 +2,11 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BreadcrumbTypes } from '@red/domain';
import { ArchivedDossiersScreenComponent } from './screens/archived-dossiers-screen/archived-dossiers-screen.component';
import { DOSSIER_ID } from '@utils/constants';
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
import { CompositeRouteGuard } from '@iqser/common-ui';
import { ARCHIVED_DOSSIERS_SERVICE } from '../../tokens';
import { DossierFilesGuard } from '@guards/dossier-files-guard';
import { FilePreviewGuard } from '../../guards/file-preview.guard';
const routes: Routes = [
{
@ -24,6 +25,16 @@ const routes: Routes = [
},
loadChildren: () => import('../dossier/screens/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
},
{
path: `:${DOSSIER_ID}/file/:${FILE_ID}`,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [DossierFilesGuard, FilePreviewGuard],
breadcrumbs: [BreadcrumbTypes.archive, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
dossiersService: ARCHIVED_DOSSIERS_SERVICE,
},
loadChildren: () => import('../dossier/screens/file-preview-screen/file-preview.module').then(m => m.FilePreviewModule),
},
];
@NgModule({

View File

@ -102,28 +102,11 @@ export class AssignReviewerApproverDialogComponent {
this._loadingService.start();
try {
if (!this.selectedUser) {
await firstValueFrom(
this._filesService.setUnassigned(
this.data.files.map(f => f.fileId),
this.dossier.id,
),
);
await firstValueFrom(this._filesService.setUnassigned(this.data.files, this.dossier.id));
} else if (this.data.mode === 'reviewer') {
await firstValueFrom(
this._filesService.setReviewerFor(
this.data.files.map(f => f.fileId),
this.dossier.id,
this.selectedUser,
),
);
await firstValueFrom(this._filesService.setReviewerFor(this.data.files, this.dossier.id, this.selectedUser));
} else {
await firstValueFrom(
this._filesService.setUnderApprovalFor(
this.data.files.map(f => f.fileId),
this.dossier.id,
this.selectedUser,
),
);
await firstValueFrom(this._filesService.setUnderApprovalFor(this.data.files, this.dossier.id, this.selectedUser));
}
} catch (error) {
this._toaster.error(_('error.http.generic'), { params: error });

View File

@ -25,10 +25,10 @@ export class DocumentInfoDialogComponent extends BaseDialogComponent implements
private readonly _filesService: FilesService,
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<DocumentInfoDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly data: File,
@Inject(MAT_DIALOG_DATA) readonly file: File,
) {
super(_injector, _dialogRef);
this._dossier = this._activeDossiersService.find(this.data.dossierId);
this._dossier = this._activeDossiersService.find(this.file.dossierId);
}
async ngOnInit() {
@ -42,11 +42,11 @@ export class DocumentInfoDialogComponent extends BaseDialogComponent implements
async save() {
const attributeIdToValue = {
...this.data.fileAttributes?.attributeIdToValue,
...this.file.fileAttributes?.attributeIdToValue,
...this.form.getRawValue(),
};
await firstValueFrom(this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId));
await firstValueFrom(this._filesService.reload(this.data.dossierId, this.data.fileId));
await firstValueFrom(this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file.dossierId, this.file.fileId));
await firstValueFrom(this._filesService.reload(this.file.dossierId, this.file));
this._dialogRef.close(true);
}
@ -55,7 +55,7 @@ export class DocumentInfoDialogComponent extends BaseDialogComponent implements
this.attributes.reduce(
(acc, attr) => ({
...acc,
[attr.id]: [this.data.fileAttributes?.attributeIdToValue[attr.id]],
[attr.id]: [this.file.fileAttributes?.attributeIdToValue[attr.id]],
}),
{},
),

View File

@ -6,6 +6,7 @@ import {
ConfirmationDialogInput,
DefaultListingServices,
IListable,
IRouterPath,
ListingComponent,
LoadingService,
SortingOrders,
@ -25,7 +26,7 @@ import { workflowFileStatusTranslations } from '../../../translations/file-statu
import { PermissionsService } from '@services/permissions.service';
import { UserService } from '@services/user.service';
interface FileListItem extends IFile, IListable {
interface FileListItem extends IFile, IListable, IRouterPath {
readonly canHardDelete: boolean;
readonly canRestore: boolean;
readonly restoreDate: string;
@ -129,14 +130,13 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
disabledFn = (file: FileListItem) => !file.canRestore;
private async _restore(files: FileListItem[]): Promise<void> {
const fileIds = files.map(f => f.fileId);
await firstValueFrom(this._fileManagementService.restore(fileIds, this.dossier.id));
const fileIds = files.map(f => f.id);
await firstValueFrom(this._fileManagementService.restore(files, this.dossier.id));
this._removeFromList(fileIds);
await firstValueFrom(this._filesService.loadAll(files[0].dossierId));
}
private async _hardDelete(files: FileListItem[]) {
const fileIds = files.map(f => f.fileId);
const fileIds = files.map(f => f.id);
await firstValueFrom(this._fileManagementService.hardDelete(this.dossier.id, fileIds));
this._removeFromList(fileIds);
}
@ -151,7 +151,7 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
}
private _toListItem(_file: IFile): FileListItem {
const file = new File(_file, this._userService.getNameForId(_file.assignee));
const file = new File(_file, this._userService.getNameForId(_file.assignee), this.dossier.routerPath);
const restoreDate = this._getRestoreDate(_file.softDeleted);
return {
id: file.fileId,

View File

@ -182,7 +182,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
this.#canToggleAnalysis = this._permissionsService.canToggleAnalysis(this.selectedFiles);
this.#canOcr = this.selectedFiles.reduce((acc, file) => acc && file.canBeOCRed, true);
this.#canOcr = this._permissionsService.canOcrFile(this.selectedFiles);
this.#canSetToUnderReview = this._permissionsService.canSetUnderReview(this.selectedFiles) && !isWorkflow;

View File

@ -43,7 +43,7 @@ export class DossierDetailsStatsComponent implements OnInit {
openEditDossierDialog(section: string): void {
const data = { dossierId: this.dossier.dossierId, section };
this._dialogService.openDialog('editDossier', null, data, async () => {
await firstValueFrom(this._filesService.loadAll(this.dossier.dossierId));
await firstValueFrom(this._filesService.loadAll(this.dossier.dossierId, this.dossier.routerPath));
});
}
}

View File

@ -14,6 +14,7 @@
<iqser-circle-button
(action)="downloadDossierAsCSV()"
*ngIf="permissionsService.canDownloadCsvReport(dossier)"
[disabled]="listingService.areSomeSelected$ | async"
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
icon="iqser:csv"

View File

@ -57,7 +57,7 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
async reanalyseDossier() {
this._loadingService.start();
try {
await firstValueFrom(this._reanalysisService.reanalyzeDossier(this.dossier.dossierId, true));
await firstValueFrom(this._reanalysisService.reanalyzeDossier(this.dossier, true));
this._toaster.success(_('dossier-overview.reanalyse-dossier.success'));
} catch (e) {
this._toaster.error(_('dossier-overview.reanalyse-dossier.error'));

View File

@ -142,7 +142,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
this.addSubscription = this._dossiersService.dossierFileChanges$
.pipe(
filter(dossierId => dossierId === this.dossierId),
switchMap(dossierId => this._filesService.loadAll(dossierId)),
switchMap(dossierId => this._filesService.loadAll(dossierId, this._dossiersService.routerPath)),
)
.subscribe();

View File

@ -29,13 +29,7 @@ export class BulkActionsService {
this._assignFiles(files, 'approver', true);
} else {
this._loadingService.start();
await firstValueFrom(
this._filesService.setUnderApprovalFor(
files.map(f => f.id),
dossier.id,
dossier.approverIds[0],
),
);
await firstValueFrom(this._filesService.setUnderApprovalFor(files, dossier.id, dossier.approverIds[0]));
this._loadingService.stop();
}
}
@ -46,12 +40,7 @@ export class BulkActionsService {
async ocr(files: File[]) {
this._loadingService.start();
await firstValueFrom(
this._reanalysisService.ocrFiles(
files.map(f => f.fileId),
files[0].dossierId,
),
);
await firstValueFrom(this._reanalysisService.ocrFiles(files, files[0].dossierId));
this._loadingService.stop();
}
@ -65,12 +54,7 @@ export class BulkActionsService {
}),
async () => {
this._loadingService.start();
await firstValueFrom(
this._fileManagementService.delete(
files.map(item => item.fileId),
files[0].dossierId,
),
);
await firstValueFrom(this._fileManagementService.delete(files, files[0].dossierId));
this._loadingService.stop();
},
);
@ -78,9 +62,8 @@ export class BulkActionsService {
async reanalyse(files: File[]) {
this._loadingService.start();
const fileIds = files.map(file => file.fileId);
await firstValueFrom(
this._reanalysisService.reanalyzeFilesForDossier(fileIds, files[0].dossierId, { force: true, triggeredByUser: true }),
this._reanalysisService.reanalyzeFilesForDossier(files, files[0].dossierId, { force: true, triggeredByUser: true }),
);
this._loadingService.stop();
}
@ -93,24 +76,13 @@ export class BulkActionsService {
async toggleAnalysis(files: File[], excluded: boolean) {
this._loadingService.start();
await firstValueFrom(
this._reanalysisService.toggleAnalysis(
files[0].dossierId,
files.map(f => f.id),
excluded,
),
);
await firstValueFrom(this._reanalysisService.toggleAnalysis(files[0].dossierId, files, excluded));
this._loadingService.stop();
}
async backToUnderReview(files: File[]): Promise<void> {
this._loadingService.start();
await firstValueFrom(
this._filesService.setUnderReviewFor(
files.map(f => f.id),
files[0].dossierId,
),
);
await firstValueFrom(this._filesService.setUnderReviewFor(files, files[0].dossierId));
this._loadingService.stop();
}
@ -135,23 +107,13 @@ export class BulkActionsService {
}),
async () => {
this._loadingService.start();
await firstValueFrom(
this._filesService.setApprovedFor(
files.map(f => f.id),
files[0].dossierId,
),
);
await firstValueFrom(this._filesService.setApprovedFor(files, files[0].dossierId));
this._loadingService.stop();
},
);
} else {
this._loadingService.start();
await firstValueFrom(
this._filesService.setApprovedFor(
files.map(f => f.id),
files[0].dossierId,
),
);
await firstValueFrom(this._filesService.setApprovedFor(files, files[0].dossierId));
this._loadingService.stop();
}
}

View File

@ -2,15 +2,15 @@
<iqser-circle-button
(action)="openEditDossierDialog($event, dossier.dossierId)"
*ngIf="currentUser.isUser"
[icon]="currentUser.isManager ? 'iqser:edit' : 'red:info'"
[scrollableParentView]="scrollableParentView"
[tooltip]="(currentUser.isManager ? 'dossier-listing.edit.action' : 'dossier-listing.dossier-info.action') | translate"
[type]="circleButtonTypes.dark"
[icon]="currentUser.isManager ? 'iqser:edit' : 'red:info'"
iqserHelpMode="edit_dossier"
[scrollableParentView]="scrollableParentView"
></iqser-circle-button>
<iqser-circle-button
(action)="reanalyseDossier($event, dossier.dossierId)"
(action)="reanalyseDossier($event, dossier)"
*ngIf="displayReanalyseBtn"
[tooltip]="'dossier-listing.reanalyse.action' | translate"
[type]="circleButtonTypes.dark"
@ -19,8 +19,8 @@
<redaction-file-download-btn
[files]="files"
[scrollableParentView]="scrollableParentView"
[type]="circleButtonTypes.dark"
iqserHelpMode="download_dossier"
[scrollableParentView]="scrollableParentView"
></redaction-file-download-btn>
</div>

View File

@ -56,8 +56,8 @@ export class DossiersListingActionsComponent implements OnChanges {
this._dialogService.openDialog('editDossier', $event, { dossierId });
}
async reanalyseDossier($event: MouseEvent, id: string): Promise<void> {
async reanalyseDossier($event: MouseEvent, dossier: Dossier): Promise<void> {
$event.stopPropagation();
await firstValueFrom(this._reanalysisService.reanalyzeDossier(id));
await firstValueFrom(this._reanalysisService.reanalyzeDossier(dossier));
}
}

View File

@ -5,7 +5,7 @@ import { MultiSelectService } from '../../services/multi-select.service';
import { AnnotationReferencesService } from '../../services/annotation-references.service';
import { ViewModeService } from '../../services/view-mode.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { UserPreferenceService } from '../../../../../../services/user-preference.service';
import { UserPreferenceService } from '@services/user-preference.service';
@Component({
selector: 'redaction-annotations-list',

View File

@ -34,7 +34,7 @@
<div *ngIf="state.isReadonly$ | async" class="justify-center banner read-only d-flex">
<div class="flex-center">
<mat-icon class="primary-white" svgIcon="red:read-only"></mat-icon>
<span class="read-only-text" translate="readonly"></span>
<span [translate]="(state.dossier$ | async).isActive ? 'readonly' : 'readonly-archived'" class="read-only-text"></span>
</div>
</div>

View File

@ -67,7 +67,7 @@ export class PageExclusionComponent {
endPage,
};
});
const excludePages$ = this._reanalysisService.excludePages({ pageRanges }, this._state.dossierId, this._state.fileId);
const excludePages$ = this._reanalysisService.excludePages({ pageRanges }, file.dossierId, file);
await firstValueFrom(excludePages$);
this._inputComponent.reset();
} catch (e) {
@ -78,12 +78,13 @@ export class PageExclusionComponent {
async includePagesRange(range: IPageRange): Promise<void> {
this._loadingService.start();
const file = await this._state.file;
const includePages$ = this._reanalysisService.includePages(
{
pageRanges: [range],
},
this._state.dossierId,
this._state.fileId,
file.dossierId,
file,
);
await firstValueFrom(includePages$);
this._inputComponent.reset();

View File

@ -96,15 +96,15 @@ export class UserManagementComponent {
const assigneeId = typeof user === 'string' ? user : user?.id;
const reviewerName = this.userService.getNameForId(assigneeId);
const { dossierId, fileId, filename } = file;
const { dossierId, filename } = file;
this.loadingService.start();
if (!assigneeId) {
await firstValueFrom(this.filesService.setUnassigned([fileId], dossierId));
await firstValueFrom(this.filesService.setUnassigned([file], dossierId));
} else if (file.isNew || file.isUnderReview) {
await firstValueFrom(this.filesService.setReviewerFor([fileId], dossierId, assigneeId));
await firstValueFrom(this.filesService.setReviewerFor([file], dossierId, assigneeId));
} else if (file.isUnderApproval) {
await firstValueFrom(this.filesService.setUnderApprovalFor([fileId], dossierId, assigneeId));
await firstValueFrom(this.filesService.setUnderApprovalFor([file], dossierId, assigneeId));
}
this.loadingService.stop();

View File

@ -219,7 +219,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
const file = await this.stateService.file;
if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) {
const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([this.fileId], this.dossierId, { force: true });
const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
await firstValueFrom(reanalyzeFiles);
}
@ -416,7 +416,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
async annotationsChangedByReviewAction(annotation?: AnnotationWrapper) {
this.multiSelectService.deactivate();
const fileReloaded = await firstValueFrom(this._filesService.reload(this.dossierId, this.fileId));
const file = await this.stateService.file;
const fileReloaded = await firstValueFrom(this._filesService.reload(this.dossierId, file));
if (!fileReloaded) {
await this._reloadAnnotationsForPage(annotation?.pageNumber ?? this.activeViewerPage);
}
@ -541,7 +542,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private _subscribeToFileUpdates(): void {
this.addActiveScreenSubscription = timer(0, 5000)
.pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId)))
.pipe(
switchMap(() => this.stateService.file$),
switchMap(file => this._filesService.reload(this.dossierId, file)),
)
.subscribe();
this.addActiveScreenSubscription = this._activeDossiersService

View File

@ -1,8 +1,7 @@
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { BehaviorSubject, firstValueFrom, Observable, pairwise, switchMap } from 'rxjs';
import { FileDataModel } from '@models/file/file-data.model';
import { Dossier, File } from '@red/domain';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { ActivatedRoute } from '@angular/router';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { PermissionsService } from '@services/permissions.service';
@ -10,6 +9,8 @@ import { boolFactory, shareLast } from '@iqser/common-ui';
import { filter, startWith } from 'rxjs/operators';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
@Injectable()
export class FilePreviewStateService {
@ -27,19 +28,19 @@ export class FilePreviewStateService {
readonly #fileData$ = new BehaviorSubject<FileDataModel | undefined>(undefined);
constructor(
activeDossiersService: ActiveDossiersService,
filesMapService: FilesMapService,
permissionsService: PermissionsService,
activatedRoute: ActivatedRoute,
private readonly _fileManagementService: FileManagementService,
private readonly _injector: Injector,
private readonly _route: ActivatedRoute,
private readonly _filesMapService: FilesMapService,
private readonly _permissionsService: PermissionsService,
) {
this.fileId = activatedRoute.snapshot.paramMap.get(FILE_ID);
this.dossierId = activatedRoute.snapshot.paramMap.get(DOSSIER_ID);
this.dossierTemplateId = activeDossiersService.find(this.dossierId).dossierTemplateId;
this.fileId = _route.snapshot.paramMap.get(FILE_ID);
this.dossierId = _route.snapshot.paramMap.get(DOSSIER_ID);
this.dossierTemplateId = this._dossiersService.find(this.dossierId).dossierTemplateId;
this.dossier$ = activeDossiersService.getEntityChanged$(this.dossierId);
this.file$ = filesMapService.watch$(this.dossierId, this.fileId);
[this.isReadonly$, this.isWritable$] = boolFactory(this.file$, file => !permissionsService.canPerformAnnotationActions(file));
this.dossier$ = this._dossiersService.getEntityChanged$(this.dossierId);
this.file$ = _filesMapService.watch$(this.dossierId, this.fileId);
[this.isReadonly$, this.isWritable$] = boolFactory(this.file$, file => !_permissionsService.canPerformAnnotationActions(file));
this.fileData$ = this.#fileData$.asObservable().pipe(filter(value => !!value));
this.blob$ = this.#blob$;
@ -65,6 +66,10 @@ export class FilePreviewStateService {
return firstValueFrom(this.blob$);
}
private get _dossiersService(): DossiersService {
return dossiersServiceResolver(this._injector);
}
get #blob$() {
return this.file$.pipe(
startWith(undefined),

View File

@ -74,6 +74,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
showUnderApproval = false;
showApprove = false;
canToggleAnalysis = false;
showToggleAnalysis = false;
showStatusBar = false;
showOpenDocument = false;
showReanalyseFilePreview = false;
@ -127,28 +128,28 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
return [
{
type: ActionTypes.circleBtn,
action: $event => this._openDeleteFileDialog($event),
action: ($event: MouseEvent) => this._openDeleteFileDialog($event),
tooltip: _('dossier-overview.delete.action'),
icon: 'iqser:trash',
show: this.showDelete,
},
{
type: ActionTypes.circleBtn,
action: $event => this._assign($event),
action: ($event: MouseEvent) => this._assign($event),
tooltip: this.assignTooltip,
icon: 'red:assign',
show: this.showAssign,
},
{
type: ActionTypes.circleBtn,
action: $event => this._assignToMe($event),
action: ($event: MouseEvent) => this._assignToMe($event),
tooltip: _('dossier-overview.assign-me'),
icon: 'red:assign-me',
show: this.showAssignToSelf,
},
{
type: ActionTypes.circleBtn,
action: $event => this._triggerImportRedactions($event),
action: ($event: MouseEvent) => this._triggerImportRedactions($event),
tooltip: _('dossier-overview.import-redactions'),
icon: 'iqser:upload',
show: this.showImportRedactions,
@ -178,21 +179,21 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
},
{
type: ActionTypes.circleBtn,
action: $event => this._setFileUnderApproval($event),
action: ($event: MouseEvent) => this._setFileUnderApproval($event),
tooltip: _('dossier-overview.under-approval'),
icon: 'red:ready-for-approval',
show: this.showUnderApproval,
},
{
type: ActionTypes.circleBtn,
action: $event => this._setFileUnderReview($event),
action: ($event: MouseEvent) => this._setFileUnderReview($event),
tooltip: _('dossier-overview.under-review'),
icon: 'red:undo',
show: this.showUnderReview,
},
{
type: ActionTypes.circleBtn,
action: $event => this.setFileApproved($event),
action: ($event: MouseEvent) => this.setFileApproved($event),
tooltip: this.file.canBeApproved ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'),
icon: 'red:approved',
disabled: !this.file.canBeApproved,
@ -200,14 +201,14 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
},
{
type: ActionTypes.circleBtn,
action: $event => this.toggleAutomaticAnalysis($event),
action: ($event: MouseEvent) => this.toggleAutomaticAnalysis($event),
tooltip: _('dossier-overview.disable-auto-analysis'),
icon: 'red:stop',
show: this.canDisableAutoAnalysis,
},
{
type: ActionTypes.circleBtn,
action: $event => this._reanalyseFile($event),
action: ($event: MouseEvent) => this._reanalyseFile($event),
tooltip: _('file-preview.reanalyse-notification'),
tooltipClass: 'warn small',
icon: 'iqser:refresh',
@ -215,7 +216,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
},
{
type: ActionTypes.circleBtn,
action: $event => this.toggleAutomaticAnalysis($event),
action: ($event: MouseEvent) => this.toggleAutomaticAnalysis($event),
tooltip: _('dossier-overview.enable-auto-analysis'),
buttonType: this.isFilePreview ? CircleButtonTypes.warn : CircleButtonTypes.default,
icon: 'red:play',
@ -223,21 +224,21 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
},
{
type: ActionTypes.circleBtn,
action: $event => this._setFileUnderApproval($event),
action: ($event: MouseEvent) => this._setFileUnderApproval($event),
tooltip: _('dossier-overview.under-approval'),
icon: 'red:undo',
show: this.showUndoApproval,
},
{
type: ActionTypes.circleBtn,
action: $event => this._ocrFile($event),
action: ($event: MouseEvent) => this._ocrFile($event),
tooltip: _('dossier-overview.ocr-file'),
icon: 'iqser:ocr',
show: this.showOCR,
},
{
type: ActionTypes.circleBtn,
action: $event => this._reanalyseFile($event),
action: ($event: MouseEvent) => this._reanalyseFile($event),
tooltip: _('dossier-overview.reanalyse.action'),
icon: 'iqser:refresh',
show: this.showReanalyseDossierOverview,
@ -249,7 +250,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
tooltip: this.toggleTooltip,
class: { 'mr-24': this.isDossierOverviewList },
checked: !this.file.excluded,
show: true,
show: this.showToggleAnalysis,
},
].filter(btn => btn.show);
}
@ -331,7 +332,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
this._loadingService.start();
try {
const dossier = this._activeDossiersService.find(this.file.dossierId);
await firstValueFrom(this._fileManagementService.delete([this.file.fileId], this.file.dossierId));
await firstValueFrom(this._fileManagementService.delete([this.file], this.file.dossierId));
await this._router.navigate([dossier.routerLink]);
} catch (error) {
this._toaster.error(_('error.http.generic'), { params: error });
@ -360,7 +361,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
force: true,
triggeredByUser: true,
};
await firstValueFrom(this._reanalysisService.reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, params));
await firstValueFrom(this._reanalysisService.reanalyzeFilesForDossier([this.file], this.file.dossierId, params));
}
private async toggleAutomaticAnalysis($event: MouseEvent) {
@ -378,7 +379,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
private async _ocrFile($event: MouseEvent) {
$event.stopPropagation();
this._loadingService.start();
await firstValueFrom(this._reanalysisService.ocrFiles([this.file.fileId], this.file.dossierId));
await firstValueFrom(this._reanalysisService.ocrFiles([this.file], this.file.dossierId));
this._loadingService.stop();
}
@ -388,7 +389,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
private async _toggleAnalysis() {
this._loadingService.start();
await firstValueFrom(this._reanalysisService.toggleAnalysis(this.file.dossierId, [this.file.fileId], !this.file.excluded));
await firstValueFrom(this._reanalysisService.toggleAnalysis(this.file.dossierId, [this.file], !this.file.excluded));
this._loadingService.stop();
}
@ -410,8 +411,9 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
this.showApprove = this._permissionsService.isReadyForApproval(this.file) && !this.isDossierOverviewWorkflow;
this.canToggleAnalysis = this._permissionsService.canToggleAnalysis(this.file);
this.showToggleAnalysis = this._permissionsService.showToggleAnalysis(this.file);
this.showDelete = this._permissionsService.canDeleteFile(this.file);
this.showOCR = this.file.canBeOCRed;
this.showOCR = this._permissionsService.canOcrFile(this.file);
this.canReanalyse = this._permissionsService.canReanalyseFile(this.file);
this.canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis([this.file]);
this.canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis([this.file]);
@ -437,7 +439,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
private async _setFileApproved() {
this._loadingService.start();
await firstValueFrom(this._filesService.setApprovedFor([this.file.id], this.file.dossierId));
await firstValueFrom(this._filesService.setApprovedFor([this.file], this.file.dossierId));
this._loadingService.stop();
}
}

View File

@ -81,28 +81,11 @@ export class FileAssignService {
this._loadingService.start();
try {
if (!userId) {
await firstValueFrom(
this._filesService.setUnassigned(
files.map(f => f.fileId),
files[0].dossierId,
),
);
await firstValueFrom(this._filesService.setUnassigned(files, files[0].dossierId));
} else if (mode === 'reviewer') {
await firstValueFrom(
this._filesService.setReviewerFor(
files.map(f => f.fileId),
files[0].dossierId,
userId,
),
);
await firstValueFrom(this._filesService.setReviewerFor(files, files[0].dossierId, userId));
} else {
await firstValueFrom(
this._filesService.setUnderApprovalFor(
files.map(f => f.fileId),
files[0].dossierId,
userId,
),
);
await firstValueFrom(this._filesService.setUnderApprovalFor(files, files[0].dossierId, userId));
}
} catch (error) {
this._toaster.error(_('error.http.generic'), { params: error });
@ -117,11 +100,7 @@ export class FileAssignService {
private _assignReviewerToCurrentUser(files: File[]): Observable<any> {
this._loadingService.start();
return this._filesService
.setReviewerFor(
files.map(f => f.fileId),
files[0].dossierId,
this._userService.currentUser.id,
)
.setReviewerFor(files, files[0].dossierId, this._userService.currentUser.id)
.pipe(tap(() => this._loadingService.stop()));
}
}

View File

@ -38,7 +38,7 @@ export class FileDownloadBtnComponent implements OnChanges {
async downloadFiles($event: MouseEvent) {
$event.stopPropagation();
const dossierId = this.files[0].dossierId;
const filesIds = this.files.map(f => f.fileId);
const filesIds = this.files.map(f => f.id);
await firstValueFrom(this._fileDownloadService.downloadFiles(filesIds, dossierId));
this._toaster.info(_('download-status.queued'));
}

View File

@ -29,6 +29,10 @@ export class ExpandableFileActionsComponent implements OnChanges {
private readonly _permissionsService: PermissionsService,
) {}
get scrollableParentView(): ScrollableParentView {
return this.helpModeKey === 'document_features' ? ScrollableParentViews.VIRTUAL_SCROLL : undefined;
}
ngOnChanges(changes: SimpleChanges) {
if (changes.actions || changes.maxWidth) {
if (this.maxWidth) {
@ -59,12 +63,8 @@ export class ExpandableFileActionsComponent implements OnChanges {
private async _downloadFiles($event: MouseEvent, files: File[]) {
$event.stopPropagation();
const dossierId = files[0].dossierId;
const filesIds = files.map(f => f.fileId);
const filesIds = files.map(f => f.id);
await firstValueFrom(this._fileDownloadService.downloadFiles(filesIds, dossierId));
this._toaster.info(_('download-status.queued'));
}
get scrollableParentView(): ScrollableParentView {
return this.helpModeKey === 'document_features' ? ScrollableParentViews.VIRTUAL_SCROLL : undefined;
}
}

View File

@ -42,7 +42,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
super(_injector, 'upload');
const fileFetch$ = this._fetchFiles$.pipe(
throttleTime(250),
switchMap(dossierId => this._filesService.loadAll(dossierId)),
switchMap(dossierId => this._filesService.loadAll(dossierId, 'dossiers')),
);
this._subscriptions.add(fileFetch$.subscribe());
const interval$ = interval(2500).pipe(tap(() => this._handleUploads()));

View File

@ -90,7 +90,8 @@ export abstract class DossiersService extends EntitiesService<Dossier, IDossier>
dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId));
}
private _load(id: string, queryParams?: List<QueryParam>): Observable<DossierStats[]> {
private _load(id: string): Observable<DossierStats[]> {
const queryParams: List<QueryParam> = [{ key: 'includeArchived', value: this._path === 'archived-dossiers' }];
return super._getOne([id], this._defaultModelPath, queryParams).pipe(
map(entity => new Dossier(entity, 'dossiers')),
tap(dossier => this.replace(dossier)),

View File

@ -1,4 +1,4 @@
import { GenericService, HeadersConfiguration, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { GenericService, HeadersConfiguration, IRouterPath, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { Injectable, Injector } from '@angular/core';
import { HttpHeaders, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
@ -19,8 +19,10 @@ export class FileManagementService extends GenericService<unknown> {
}
@Validate()
delete(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string) {
return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
delete(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, routerPath)));
}
@Validate()
@ -32,8 +34,10 @@ export class FileManagementService extends GenericService<unknown> {
}
@Validate()
restore(@RequiredParam() body: List, @RequiredParam() dossierId: string) {
return this._post(body, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
restore(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, routerPath)));
}
downloadOriginalFile(dossierId: string, fileId: string, observe?: 'body', indicator?: string): Observable<Blob>;

View File

@ -12,7 +12,11 @@ export class FilesMapService extends EntitiesMapService<File, IFile> {
replaceFiles(files: File[], property: keyof IFile, generateValue: Function) {
const newFiles = files.map(
file =>
new File({ ...file, [property]: generateValue(file[property]), lastUpdated: new Date().toISOString() }, file.reviewerName),
new File(
{ ...file, [property]: generateValue(file[property]), lastUpdated: new Date().toISOString() },
file.reviewerName,
file.routerPath,
),
);
this.replace(newFiles);
}

View File

@ -1,5 +1,5 @@
import { Injectable, Injector } from '@angular/core';
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
import { EntitiesService, IRouterPath, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
import { File, IFile } from '@red/domain';
import { Observable } from 'rxjs';
import { UserService } from '../user.service';
@ -21,40 +21,50 @@ export class FilesService extends EntitiesService<File, IFile> {
}
/** Reload dossier files + stats. */
loadAll(dossierId: string) {
const files$ = this.getFor(dossierId).pipe(mapEach(file => new File(file, this._userService.getNameForId(file.assignee))));
loadAll(dossierId: string, routerPath: string) {
const files$ = this.getFor(dossierId).pipe(
mapEach(file => new File(file, this._userService.getNameForId(file.assignee), routerPath)),
);
const loadStats$ = files$.pipe(switchMap(files => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(files))));
return loadStats$.pipe(tap(files => this._filesMapService.set(dossierId, files)));
}
reload(dossierId: string, fileId: string): Observable<boolean> {
return super._getOne([dossierId, fileId]).pipe(
map(file => new File(file, this._userService.getNameForId(file.assignee))),
switchMap(file => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(file))),
map(file => this._filesMapService.replace([file])),
reload(dossierId: string, file: File): Observable<boolean> {
return super._getOne([dossierId, file.id]).pipe(
map(_file => new File(_file, this._userService.getNameForId(_file.assignee), file.routerPath)),
switchMap(_file => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(_file))),
map(_file => this._filesMapService.replace([_file])),
);
}
@Validate()
setUnassigned(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string) {
setUnassigned(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
const url = `${this._defaultModelPath}/set-assignee/${dossierId}/bulk`;
return this._post<unknown>(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId)));
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
return this._post<unknown>(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId, routerPath)));
}
@Validate()
setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, assigneeId: string) {
setUnderApprovalFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string, assigneeId: string) {
const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`;
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId)));
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(
switchMap(() => this.loadAll(dossierId, routerPath)),
);
}
/**
* Assigns a reviewer for a list of files.
*/
@Validate()
setReviewerFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string, assigneeId: string) {
setReviewerFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string, assigneeId: string) {
const url = `${this._defaultModelPath}/under-review/${dossierId}/bulk`;
return this._post<unknown>(filesIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(
switchMap(() => this.loadAll(dossierId)),
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(
switchMap(() => this.loadAll(dossierId, routerPath)),
);
}
@ -62,9 +72,11 @@ export class FilesService extends EntitiesService<File, IFile> {
* Sets the status APPROVED for a list of files.
*/
@Validate()
setApprovedFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string) {
return this._post<unknown>(filesIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`).pipe(
switchMap(() => this.loadAll(dossierId)),
setApprovedFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
return this._post<unknown>(fileIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`).pipe(
switchMap(() => this.loadAll(dossierId, routerPath)),
);
}
@ -72,9 +84,11 @@ export class FilesService extends EntitiesService<File, IFile> {
* Sets the status UNDER_REVIEW for a list of files.
*/
@Validate()
setUnderReviewFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string) {
return this._post<unknown>(filesIds, `${this._defaultModelPath}/under-review/${dossierId}/bulk`).pipe(
switchMap(() => this.loadAll(dossierId)),
setUnderReviewFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
return this._post<unknown>(fileIds, `${this._defaultModelPath}/under-review/${dossierId}/bulk`).pipe(
switchMap(() => this.loadAll(dossierId, routerPath)),
);
}

View File

@ -1,6 +1,6 @@
import { Injectable, Injector } from '@angular/core';
import { GenericService } from '@iqser/common-ui';
import { IMatchedDocument, ISearchInput, ISearchRequest, ISearchResponse } from '@red/domain';
import { Dossier, IMatchedDocument, ISearchInput, ISearchRequest, ISearchResponse } from '@red/domain';
import { Observable, of, zip } from 'rxjs';
import { mapTo, switchMap } from 'rxjs/operators';
import { ActiveDossiersService } from './active-dossiers.service';
@ -44,11 +44,11 @@ export class PlatformSearchService extends GenericService<ISearchResponse> {
const fileNotLoaded = ({ dossierId, fileId }: IMatchedDocument) => !this._filesMapService.get(dossierId, fileId);
const dossiersWithNotLoadedFiles = documentsOfActiveDossiers.filter(fileNotLoaded).map(document => document.dossierId);
const dossierIds = Array.from(new Set(dossiersWithNotLoadedFiles));
return dossierIds.length ? this._loadFilesFor$(dossierIds).pipe(mapTo(searchResponse)) : of(searchResponse);
const dossiers = Array.from(new Set(dossiersWithNotLoadedFiles)).map(dossierId => this._activeDossiersService.find(dossierId));
return dossiers.length ? this._loadFilesFor$(dossiers).pipe(mapTo(searchResponse)) : of(searchResponse);
}
private _loadFilesFor$(dossierIds: string[]) {
return zip(...dossierIds.map(dossierId => this._filesService.loadAll(dossierId)));
private _loadFilesFor$(dossiers: Dossier[]) {
return zip(...dossiers.map(dossier => this._filesService.loadAll(dossier.id, dossier.routerPath)));
}
}

View File

@ -30,6 +30,10 @@ export class PermissionsService {
return dossier.isActive;
}
canDownloadCsvReport(dossier: Dossier): boolean {
return dossier.isActive;
}
canEditFileAttributes(file: File): boolean {
const dossier = this._getDossier(file);
return ((file.isUnderReview || file.isNew) && this.isDossierMember(dossier)) || (file.isUnderApproval && this.isApprover(dossier));
@ -41,6 +45,11 @@ export class PermissionsService {
return sameState && files.reduce((acc, _file) => this._canToggleAnalysis(_file) && acc, true);
}
showToggleAnalysis(file: File | File[]): boolean {
const files = file instanceof File ? [file] : file;
return this._isActive(files[0]);
}
canReanalyseFile(file: File | File[]): boolean {
const files = file instanceof File ? [file] : file;
return files.reduce((acc, _file) => this._canReanalyseFile(_file) && acc, true);
@ -64,6 +73,12 @@ export class PermissionsService {
return files.reduce((acc, _file) => this._canDeleteFile(_file, dossier) && acc, true);
}
canOcrFile(file: File | File[]): boolean {
const files = file instanceof File ? [file] : file;
const dossier = this._getDossier(files[0]);
return files.reduce((acc, _file) => this._canOcrFile(_file, dossier) && acc, true);
}
canAssignToSelf(file: File | File[]): boolean {
const files = file instanceof File ? [file] : file;
const dossier = this._getDossier(files[0]);
@ -120,7 +135,7 @@ export class PermissionsService {
// TODO: Remove '?', after we make sure file is loaded before page
canPerformAnnotationActions(file: File): boolean {
return !file.excluded && (file?.isUnderReview || file?.isUnderApproval) && this.isFileAssignee(file);
return this._isActive(file) && !file.excluded && (file?.isUnderReview || file?.isUnderApproval) && this.isFileAssignee(file);
}
canUndoApproval(file: File | File[]): boolean {
@ -171,54 +186,60 @@ export class PermissionsService {
}
canImportRedactions(file: File) {
return (this.isFileAssignee(file) || this.isApprover(this._getDossier(file))) && !file.isApproved;
return this._isActive(file) && (this.isFileAssignee(file) || this.isApprover(this._getDossier(file))) && !file.isApproved;
}
private _canOcrFile(file: File, dossier: Dossier): boolean {
return dossier.isActive && file.canBeOCRed;
}
private _canToggleAnalysis(file: File): boolean {
return this.isFileAssignee(file) && (file.isNew || file.isUnderReview || file.isUnderApproval);
return this._isActive(file) && this.isFileAssignee(file) && (file.isNew || file.isUnderReview || file.isUnderApproval);
}
// https://jira.iqser.com/browse/RED-2787
private _canDeleteFile(file: File, dossier: Dossier): boolean {
return (
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 &&
(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))))
);
}
private _canReanalyseFile(file: File): boolean {
return this.isReviewerOrApprover(file) && file.analysisRequired;
return this._isActive(file) && this.isReviewerOrApprover(file) && file.analysisRequired;
}
private _canEnableAutoAnalysis(file: File): boolean {
return file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id;
return this._isActive(file) && file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id;
}
private _canDisableAutoAnalysis(file: File): boolean {
return !file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id;
return this._isActive(file) && !file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id;
}
private _canAssignToSelf(file: File, dossier: Dossier): boolean {
const precondition = this.isDossierMember(dossier) && !this.isFileAssignee(file) && !file.isError && !file.isProcessing;
const precondition =
this._isActive(file) && this.isDossierMember(dossier) && !this.isFileAssignee(file) && !file.isError && !file.isProcessing;
return precondition && (file.isNew || file.isUnderReview || (file.isUnderApproval && this.isApprover(dossier)));
}
private _canSetUnderApproval(file: File): boolean {
return file.isUnderReview && this.isReviewerOrApprover(file);
return this._isActive(file) && file.isUnderReview && this.isReviewerOrApprover(file);
}
private _canUndoApproval(file: File, dossier: Dossier): boolean {
return file.isApproved && this.isApprover(dossier);
return this._isActive(file) && file.isApproved && this.isApprover(dossier);
}
private _canBeApproved(file: File): boolean {
return file.canBeApproved;
return this._isActive(file) && file.canBeApproved;
}
private _canAssignUser(file: File, dossier: Dossier) {
const precondition = !file.isProcessing && !file.isError && !file.isApproved && this.isApprover(dossier);
const precondition = this._isActive(file) && !file.isProcessing && !file.isError && !file.isApproved && this.isApprover(dossier);
if (precondition) {
if ((file.isNew || file.isUnderReview) && dossier.hasReviewers) {
@ -232,14 +253,20 @@ export class PermissionsService {
}
private _canUnassignUser(file: File, dossier: Dossier) {
return (file.isUnderReview || file.isUnderApproval) && (this.isFileAssignee(file) || this.isApprover(dossier));
return (
this._isActive(file) && (file.isUnderReview || file.isUnderApproval) && (this.isFileAssignee(file) || this.isApprover(dossier))
);
}
private _canSetUnderReview(file: File): boolean {
return file.isUnderApproval;
return this._isActive(file) && file.isUnderApproval;
}
private _getDossier(file: File): Dossier {
return this._dossiersService.find(file.dossierId);
}
private _isActive(file: File): boolean {
return this._getDossier(file).isActive;
}
}

View File

@ -1,6 +1,6 @@
import { Injectable, Injector } from '@angular/core';
import { GenericService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
import { File, IPageExclusionRequest } from '@red/domain';
import { GenericService, IRouterPath, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
import { Dossier, File, IPageExclusionRequest } from '@red/domain';
import { catchError, switchMap, tap } from 'rxjs/operators';
import { FilesService } from './entity-services/files.service';
import { FilesMapService } from './entity-services/files-map.service';
@ -26,17 +26,19 @@ export class ReanalysisService extends GenericService<unknown> {
}
@Validate()
excludePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `exclude-pages/${dossierId}/${fileId}`).pipe(switchMap(() => this._filesService.reload(dossierId, fileId)));
excludePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() file: File) {
return this._post(body, `exclude-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file)));
}
@Validate()
includePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `include-pages/${dossierId}/${fileId}`).pipe(switchMap(() => this._filesService.reload(dossierId, fileId)));
includePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() file: File) {
return this._post(body, `include-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file)));
}
@Validate()
reanalyzeFilesForDossier(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) {
reanalyzeFilesForDossier(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) {
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
const queryParams: QueryParam[] = [];
if (params?.force) {
queryParams.push({ key: 'force', value: true });
@ -45,18 +47,22 @@ export class ReanalysisService extends GenericService<unknown> {
queryParams.push({ key: 'triggeredByUser', value: true });
}
return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(
switchMap(() => this._filesService.loadAll(dossierId, routerPath)),
);
}
@Validate()
toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() fileIds: string[], excluded?: boolean) {
toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: List<IRouterPath>, excluded?: boolean) {
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
const queryParams: QueryParam[] = [];
if (excluded) {
queryParams.push({ key: 'excluded', value: excluded });
}
return this._post(fileIds, `toggle-analysis/${dossierId}/bulk`, queryParams).pipe(
switchMap(() => this._filesService.loadAll(dossierId)),
switchMap(() => this._filesService.loadAll(dossierId, routerPath)),
);
}
@ -80,17 +86,24 @@ export class ReanalysisService extends GenericService<unknown> {
}
@Validate()
ocrFiles(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string) {
return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
ocrFiles(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
const fileIds = files.map(f => f.id);
const routerPath: string = files[0].routerPath;
return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(
switchMap(() => this._filesService.loadAll(dossierId, routerPath)),
);
}
@Validate()
reanalyzeDossier(@RequiredParam() dossierId: string, force?: boolean) {
reanalyzeDossier(@RequiredParam() dossier: Dossier, force?: boolean) {
const { dossierId, routerPath } = dossier;
const queryParams: QueryParam[] = [];
if (force) {
queryParams.push({ key: 'force', value: force });
}
return this._post({}, `reanalyze/${dossierId}`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
return this._post({}, `reanalyze/${dossierId}`, queryParams).pipe(
switchMap(() => this._filesService.loadAll(dossierId, routerPath)),
);
}
}

View File

@ -1,7 +1,7 @@
{
"ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null,
"API_URL": "https://dev-04.iqser.cloud/redaction-gateway-v1",
"API_URL": "https://dev-05.iqser.cloud/redaction-gateway-v1",
"APP_NAME": "RedactManager",
"AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40",
@ -17,7 +17,7 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dev-04.iqser.cloud/auth/realms/redaction",
"OAUTH_URL": "https://dev-05.iqser.cloud/auth/realms/redaction",
"RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview"

View File

@ -322,7 +322,7 @@
},
"table-col-names": {
"dossier-status": "Dossier Status",
"last-modified": "Last Modified",
"last-modified": "Archived Time",
"name": "Name",
"owner": "Owner"
},
@ -1251,6 +1251,7 @@
"reanalyse-notification": "This document was not processed with the latest rule/dictionary set. Analyze now to get updated annotations.",
"redacted": "Preview",
"redacted-tooltip": "Redaction preview shows only redactions. Consider this a preview for the final redacted version. This view is only available if the file has no pending changes & doesn't require a reanalysis",
"reset-filters": "",
"standard": "Standard",
"standard-tooltip": "Standard Workload view shows all hints, redactions, recommendations & suggestions. This view allows editing.",
"tabs": {
@ -1587,6 +1588,7 @@
"processing": "Processing"
},
"readonly": "Read only",
"readonly-archived": "Read only (archived)",
"recategorize-image-dialog": {
"actions": {
"cancel": "Cancel",

@ -1 +1 @@
Subproject commit f480a52cc384eea5ab54fb3b7bbc5db431c1506c
Subproject commit 242789330301767bc38fe62651ea777297159bb3

View File

@ -1,9 +1,9 @@
import { IListable, List } from '@iqser/common-ui';
import { IListable, IRouterPath, List } from '@iqser/common-ui';
import { IDossier } from './dossier';
import { DossierStatus, DossierStatuses } from './types';
import { DownloadFileType } from '../shared';
export class Dossier implements IDossier, IListable {
export class Dossier implements IDossier, IListable, IRouterPath {
readonly dossierId: string;
readonly dossierTemplateId: string;
readonly ownerId: string;

View File

@ -1,10 +1,10 @@
import { Entity } from '@iqser/common-ui';
import { Entity, IRouterPath } from '@iqser/common-ui';
import { StatusSorter } from '../shared';
import { isProcessingStatuses, ProcessingFileStatus, ProcessingFileStatuses, WorkflowFileStatus, WorkflowFileStatuses } from './types';
import { IFile } from './file';
import { FileAttributes } from '../file-attributes';
export class File extends Entity<IFile> implements IFile {
export class File extends Entity<IFile> implements IFile, IRouterPath {
readonly added?: string;
readonly allManualRedactionsApplied: boolean;
readonly analysisDuration?: number;
@ -57,7 +57,7 @@ export class File extends Entity<IFile> implements IFile {
readonly canBeOpened: boolean;
readonly canBeOCRed: boolean;
constructor(file: IFile, readonly reviewerName: string) {
constructor(file: IFile, readonly reviewerName: string, readonly routerPath: string) {
super(file);
this.added = file.added;
this.allManualRedactionsApplied = !!file.allManualRedactionsApplied;
@ -125,6 +125,6 @@ export class File extends Entity<IFile> implements IFile {
}
get routerLink(): string | undefined {
return this.canBeOpened ? `/main/dossiers/${this.dossierId}/file/${this.fileId}` : undefined;
return this.canBeOpened ? `/main/${this.routerPath}/${this.dossierId}/file/${this.fileId}` : undefined;
}
}