From ccd5900e73439cf814abe928f93a1ef60aa5f300 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 21 Sep 2023 11:13:59 +0300 Subject: [PATCH] RED-7421 show correct user options in assign dialog --- ...dossier-overview-bulk-actions.component.ts | 8 +-- .../table-item/table-item.component.ts | 12 ++-- .../workflow-item/workflow-item.component.ts | 16 ++--- .../dossier-overview-screen.component.ts | 68 +++++++++---------- .../services/bulk-actions.service.ts | 36 ++++++---- .../user-management.component.ts | 16 ++--- .../file-actions/file-actions.component.ts | 21 +++++- ...sign-reviewer-approver-dialog.component.ts | 54 +++++++++------ .../services/dossiers-dialog.service.ts | 9 +-- .../services/file-assign.service.ts | 68 +++++++++++-------- .../src/app/services/files/files.service.ts | 10 +-- .../src/app/services/permissions.service.ts | 52 +++++++++----- libs/common-ui | 2 +- .../src/lib/dossiers/dossier.model.ts | 4 +- 14 files changed, 215 insertions(+), 161 deletions(-) diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts index 67999f139..3fb474e9b 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts @@ -187,12 +187,8 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { this.#canAssign = this.#canMoveToSameState && - this.selectedFiles.reduce( - (acc, file) => - (acc && this._permissionsService.canAssignUser(file, this.dossier)) || - this._permissionsService.canUnassignUser(file, this.dossier), - true, - ); + (this._permissionsService.canAssignUser(this.selectedFiles, this.dossier) || + this._permissionsService.canUnassignUser(this.selectedFiles, this.dossier)); this.#canAssignToSelf = this.#canMoveToSameState && this._permissionsService.canAssignToSelf(this.selectedFiles, this.dossier); this.#canDelete = this._permissionsService.canSoftDeleteFile(this.selectedFiles, this.dossier); diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/table-item/table-item.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/table-item/table-item.component.ts index 0447eac7d..9aa920909 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/table-item/table-item.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/table-item/table-item.component.ts @@ -1,18 +1,18 @@ import { Component, Input } from '@angular/core'; -import { Dossier, File, IFileAttributeConfig } from '@red/domain'; import { getConfig } from '@iqser/common-ui'; +import { Dossier, File, IFileAttributeConfig } from '@red/domain'; import { FileAttributesService } from '@services/entity-services/file-attributes.service'; @Component({ - selector: 'redaction-table-item [file] [dossier] [displayedAttributes] [dossierTemplateId]', + selector: 'redaction-table-item', templateUrl: './table-item.component.html', styleUrls: ['./table-item.component.scss'], }) export class TableItemComponent { - @Input() file: File; - @Input() dossier: Dossier; - @Input() displayedAttributes: IFileAttributeConfig[]; - @Input() dossierTemplateId: string; + @Input({ required: true }) file: File; + @Input({ required: true }) dossier: Dossier; + @Input({ required: true }) displayedAttributes: IFileAttributeConfig[]; + @Input({ required: true }) dossierTemplateId: string; readonly isDocumine = getConfig().IS_DOCUMINE; diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/workflow-item/workflow-item.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/workflow-item/workflow-item.component.ts index 47d74b93c..645bccc97 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/workflow-item/workflow-item.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/workflow-item/workflow-item.component.ts @@ -1,23 +1,21 @@ import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, Optional, ViewChild } from '@angular/core'; -import { Dossier, File, IFileAttributeConfig } from '@red/domain'; import { HelpModeService } from '@iqser/common-ui'; import { Debounce, trackByFactory } from '@iqser/common-ui/lib/utils'; +import { Dossier, File, IFileAttributeConfig } from '@red/domain'; import { FileAttributesService } from '@services/entity-services/file-attributes.service'; @Component({ - selector: 'redaction-workflow-item [file] [dossier] [displayedAttributes]', + selector: 'redaction-workflow-item', templateUrl: './workflow-item.component.html', styleUrls: ['./workflow-item.component.scss'], }) export class WorkflowItemComponent implements OnInit { - width: number; - trackBy = trackByFactory(); - @ViewChild('actionsWrapper', { static: true }) private _actionsWrapper: ElementRef; - - @Input() file: File; - @Input() dossier: Dossier; - @Input() displayedAttributes: IFileAttributeConfig[]; + width: number; + readonly trackBy = trackByFactory(); + @Input({ required: true }) file: File; + @Input({ required: true }) dossier: Dossier; + @Input({ required: true }) displayedAttributes: IFileAttributeConfig[]; constructor( readonly fileAttributesService: FileAttributesService, diff --git a/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts b/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts index 1bcab5677..6e3200db8 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts @@ -1,20 +1,5 @@ import { Component, ElementRef, HostListener, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; -import { - Dossier, - DOSSIER_ID, - DossierAttributeConfig, - DossierAttributeWithValue, - File, - IFileAttributeConfig, - WorkflowFileStatus, -} from '@red/domain'; -import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service'; -import { FileUploadModel } from '@upload-download/model/file-upload.model'; -import { FileUploadService } from '@upload-download/services/file-upload.service'; -import { StatusOverlayService } from '@upload-download/services/status-overlay.service'; -import { merge, Observable } from 'rxjs'; -import { filter, skip, switchMap, tap } from 'rxjs/operators'; -import { convertFiles, Files, handleFileDrop } from '@utils/index'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { CircleButtonTypes, CustomError, @@ -28,21 +13,36 @@ import { TableComponent, WorkflowConfig, } from '@iqser/common-ui'; -import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { PermissionsService } from '@services/permissions.service'; -import { FileAttributesService } from '@services/entity-services/file-attributes.service'; -import { ConfigService } from '../config.service'; -import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; -import { UserPreferenceService } from '@users/user-preference.service'; -import { FilesMapService } from '@services/files/files-map.service'; -import { FilesService } from '@services/files/files.service'; -import { BulkActionsService } from '../services/bulk-actions.service'; -import { DossiersService } from '@services/dossiers/dossiers.service'; -import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider'; -import { Roles } from '@users/roles'; import { NestedFilter } from '@iqser/common-ui/lib/filtering'; import { getParam, OnAttach, OnDetach, shareLast } from '@iqser/common-ui/lib/utils'; +import { + Dossier, + DOSSIER_ID, + DossierAttributeConfig, + DossierAttributeWithValue, + File, + IFileAttributeConfig, + WorkflowFileStatus, +} from '@red/domain'; +import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; +import { DossiersService } from '@services/dossiers/dossiers.service'; +import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service'; +import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider'; +import { FileAttributesService } from '@services/entity-services/file-attributes.service'; +import { FilesMapService } from '@services/files/files-map.service'; +import { FilesService } from '@services/files/files.service'; +import { PermissionsService } from '@services/permissions.service'; +import { FileUploadModel } from '@upload-download/model/file-upload.model'; +import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service'; +import { FileUploadService } from '@upload-download/services/file-upload.service'; +import { StatusOverlayService } from '@upload-download/services/status-overlay.service'; +import { Roles } from '@users/roles'; +import { UserPreferenceService } from '@users/user-preference.service'; +import { convertFiles, Files, handleFileDrop } from '@utils/index'; +import { merge, Observable } from 'rxjs'; +import { filter, skip, switchMap, tap } from 'rxjs/operators'; +import { ConfigService } from '../config.service'; +import { BulkActionsService } from '../services/bulk-actions.service'; @Component({ templateUrl: './dossier-overview-screen.component.html', @@ -55,7 +55,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple private readonly _needsWorkFilterTemplate: TemplateRef; @ViewChild('fileInput', { static: true }) private readonly _fileInput: ElementRef; @ViewChild(TableComponent) private readonly _tableComponent: TableComponent; - private _fileAttributeConfigs: IFileAttributeConfig[]; + #fileAttributeConfigs: IFileAttributeConfig[]; readonly listingModes = ListingModes; readonly circleButtonTypes = CircleButtonTypes; readonly tableHeaderLabel = _('dossier-overview.table-header.title'); @@ -216,8 +216,8 @@ export class DossierOverviewScreenComponent extends ListingComponent imple #updateFileAttributes() { const attributes = this._fileAttributesService.getFileAttributeConfig(this.#dossier.dossierTemplateId); - this._fileAttributeConfigs = attributes?.fileAttributeConfigs || []; - this.displayedInFileListAttributes = this._fileAttributeConfigs.filter(config => config.displayedInFileList); + this.#fileAttributeConfigs = attributes?.fileAttributeConfigs || []; + this.displayedInFileListAttributes = this.#fileAttributeConfigs.filter(config => config.displayedInFileList); this.displayedAttributes = this.displayedInFileListAttributes.filter(c => c.displayedInFileList); this.displayedWorkflowAttributes = this.#getDisplayedWorkflowAttributes(this.displayedAttributes); this.tableColumnConfigs = this.configService.tableConfig(this.displayedAttributes); @@ -227,7 +227,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple #getDisplayedWorkflowAttributes(displayedAttributes: IFileAttributeConfig[]): IFileAttributeConfig[] { let primaryAttribute = displayedAttributes.find(c => c.primaryAttribute); if (!primaryAttribute) { - primaryAttribute = this._fileAttributeConfigs.find(config => config.primaryAttribute); + primaryAttribute = this.#fileAttributeConfigs.find(config => config.primaryAttribute); return primaryAttribute ? [primaryAttribute, ...displayedAttributes] : displayedAttributes; } return displayedAttributes.sort((c1, c2) => (c1.primaryAttribute ? -1 : c2.primaryAttribute ? 1 : 0)); @@ -243,7 +243,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple #computeAllFilters() { const filterGroups = this.configService.filterGroups( this.entitiesService.all, - this._fileAttributeConfigs, + this.#fileAttributeConfigs, this.#dossier.dossierTemplateId, this._needsWorkFilterTemplate, () => this.checkedRequiredFilters, diff --git a/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts b/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts index 5399a7ce9..c099a721d 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts @@ -1,19 +1,21 @@ import { Injectable } from '@angular/core'; -import { Dossier, File, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; -import { DossiersDialogService } from '../../shared-dossiers/services/dossiers-dialog.service'; -import { IConfirmationDialogData, LoadingService } from '@iqser/common-ui'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { FilesService } from '@services/files/files.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { ReanalysisService } from '@services/reanalysis.service'; +import { IConfirmationDialogData, IqserDialog, LoadingService } from '@iqser/common-ui'; +import { Dossier, File, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; +import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { FileManagementService } from '@services/files/file-management.service'; +import { FilesService } from '@services/files/files.service'; +import { ReanalysisService } from '@services/reanalysis.service'; import { firstValueFrom } from 'rxjs'; +import { AssignReviewerApproverDialogComponent } from '../../shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component'; +import { DossiersDialogService } from '../../shared-dossiers/services/dossiers-dialog.service'; import { FileAssignService } from '../../shared-dossiers/services/file-assign.service'; @Injectable() export class BulkActionsService { constructor( private readonly _dialogService: DossiersDialogService, + private readonly _iqserDialog: IqserDialog, private readonly _loadingService: LoadingService, private readonly _activeDossiersService: ActiveDossiersService, private readonly _filesService: FilesService, @@ -23,10 +25,10 @@ export class BulkActionsService { ) {} async setToUnderApproval(files: File[]) { - const dossier = this._getDossier(files); + const dossier = this.#getDossier(files); // If more than 1 approver - show dialog and ask who to assign if (dossier.approverIds.length > 1) { - this._assignFiles(files, WorkflowFileStatuses.UNDER_APPROVAL, true); + this.#assignFiles(files, WorkflowFileStatuses.UNDER_APPROVAL, true); } else { this._loadingService.start(); await this._filesService.setUnderApproval(files, dossier.approverIds[0]); @@ -79,7 +81,7 @@ export class BulkActionsService { async backToUnderReview(files: File[]): Promise { this._loadingService.start(); - await this._filesService.setUnderReviewFor(files); + await this._filesService.setUnderReview(files); this._loadingService.stop(); } @@ -121,14 +123,22 @@ export class BulkActionsService { } assign(files: File[]): void { - this._assignFiles(files, files[0].workflowStatus, false, true); + this.#assignFiles(files, files[0].workflowStatus, false, true); } - private _getDossier(files: File[]): Dossier { + #getDossier(files: File[]): Dossier { return this._activeDossiersService.find(files[0].dossierId); } - private _assignFiles(files: File[], targetStatus: WorkflowFileStatus, ignoreChanged = false, withUnassignedOption = false): void { - this._dialogService.openDialog('assignFile', { targetStatus, files, ignoreChanged, withUnassignedOption }); + #assignFiles(files: File[], targetStatus: WorkflowFileStatus, ignoreChanged = false, withUnassignedOption = false): void { + this._iqserDialog.openDefault(AssignReviewerApproverDialogComponent, { + data: { + targetStatus, + files, + ignoreChanged, + withUnassignedOption, + }, + disableClose: false, + }); } } diff --git a/apps/red-ui/src/app/modules/file-preview/components/user-management/user-management.component.ts b/apps/red-ui/src/app/modules/file-preview/components/user-management/user-management.component.ts index 5c04bd120..623c10576 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/user-management/user-management.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/user-management/user-management.component.ts @@ -1,15 +1,15 @@ import { Component, computed } from '@angular/core'; -import { File, User } from '@red/domain'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { LoadingService, Toaster } from '@iqser/common-ui'; +import { getCurrentUser } from '@iqser/common-ui/lib/users'; +import { File, User } from '@red/domain'; +import { FilesService } from '@services/files/files.service'; import { PermissionsService } from '@services/permissions.service'; import { workflowFileStatusTranslations } from '@translations/file-status-translations'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { UserService } from '@users/user.service'; -import { FilesService } from '@services/files/files.service'; -import { FilePreviewStateService } from '../../services/file-preview-state.service'; -import { FileAssignService } from '../../../shared-dossiers/services/file-assign.service'; import { moveElementInArray } from '@utils/functions'; -import { getCurrentUser } from '@iqser/common-ui/lib/users'; +import { FileAssignService } from '../../../shared-dossiers/services/file-assign.service'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; @Component({ selector: 'redaction-user-management', @@ -22,9 +22,7 @@ export class UserManagementComponent { protected readonly _canUnassignUser = computed(() => this.permissionsService.canUnassignUser(this.state.file(), this.state.dossier())); protected readonly _canAssignOrUnassign = computed(() => this._canAssignUser() || this._canUnassignUser()); protected readonly _canAssign = computed(() => this._canAssignToSelf() || this._canAssignOrUnassign()); - protected readonly _canAssignReviewer = computed( - () => this._canAssignUser() && !this.state.file().assignee && this.state.dossier().hasReviewers, - ); + protected readonly _canAssignReviewer = computed(() => this._canAssignUser() && !this.state.file().assignee); protected readonly _usersOptions = computed(() => { const file = this.state.file(); const dossier = this.state.dossier(); diff --git a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts index 435ad2955..e3aba2db6 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts @@ -2,7 +2,15 @@ import { ChangeDetectorRef, Component, HostBinding, Injector, Input, OnChanges, import { toObservable } from '@angular/core/rxjs-interop'; import { Router } from '@angular/router'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { CircleButtonTypes, getConfig, IConfirmationDialogData, IqserPermissionsService, LoadingService, Toaster } from '@iqser/common-ui'; +import { + CircleButtonTypes, + getConfig, + IConfirmationDialogData, + IqserDialog, + IqserPermissionsService, + LoadingService, + Toaster, +} from '@iqser/common-ui'; import { TenantsService } from '@iqser/common-ui/lib/tenants'; import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { IqserTooltipPositions } from '@iqser/common-ui/lib/utils'; @@ -24,6 +32,7 @@ import { ExcludedPagesService } from '../../../file-preview/services/excluded-pa import { PageRotationService } from '../../../pdf-viewer/services/page-rotation.service'; import { ViewerHeaderService } from '../../../pdf-viewer/services/viewer-header.service'; import { ROTATION_ACTION_BUTTONS } from '../../../pdf-viewer/utils/constants'; +import { AssignReviewerApproverDialogComponent } from '../../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component'; import { DossiersDialogService } from '../../services/dossiers-dialog.service'; import { FileAssignService } from '../../services/file-assign.service'; @@ -77,6 +86,7 @@ export class FileActionsComponent implements OnChanges { private readonly _changeRef: ChangeDetectorRef, private readonly _loadingService: LoadingService, private readonly _dialogService: DossiersDialogService, + private readonly _iqserDialog: IqserDialog, private readonly _tenantsService: TenantsService, private readonly _fileAssignService: FileAssignService, private readonly _reanalysisService: ReanalysisService, @@ -349,7 +359,14 @@ export class FileActionsComponent implements OnChanges { const targetStatus = this.file.workflowStatus; const withCurrentUserAsDefault = true; const withUnassignedOption = true; - this._dialogService.openDialog('assignFile', { targetStatus, files, withCurrentUserAsDefault, withUnassignedOption }); + this._iqserDialog.openDefault(AssignReviewerApproverDialogComponent, { + data: { + targetStatus, + files, + withCurrentUserAsDefault, + withUnassignedOption, + }, + }); } async #assignToMe() { diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts index 0ebc1e6f9..413a307bc 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts @@ -1,8 +1,7 @@ -import { Component, Inject } from '@angular/core'; +import { Component } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; +import { IconButtonTypes, IqserDialogComponent, LoadingService, Toaster } from '@iqser/common-ui'; import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { File, User, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; @@ -22,7 +21,11 @@ class DialogData { @Component({ templateUrl: './assign-reviewer-approver-dialog.component.html', }) -export class AssignReviewerApproverDialogComponent { +export class AssignReviewerApproverDialogComponent extends IqserDialogComponent< + AssignReviewerApproverDialogComponent, + DialogData, + boolean +> { readonly #dossier = this._activeDossiersService.find(this.data.files[0].dossierId); readonly #isApprover = this.permissionsService.isApprover(this.#dossier); readonly iconButtonTypes = IconButtonTypes; @@ -39,9 +42,9 @@ export class AssignReviewerApproverDialogComponent { private readonly _filesService: FilesService, private readonly _loadingService: LoadingService, readonly permissionsService: PermissionsService, - private readonly _dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) readonly data: DialogData, - ) {} + ) { + super(); + } get selectedUser(): string { const value = this.form.controls.user.value; @@ -71,18 +74,13 @@ export class AssignReviewerApproverDialogComponent { get #userOptions() { const unassignUser = this.#canUnassignUser && this.data.withUnassignedOption ? ['undefined'] : []; - const cannotAssignUser = !this.permissionsService.canAssignUser(this.data.files, this.#dossier); - - if (this.mode === 'reviewer') { - if (this.#dossier.hasReviewers && cannotAssignUser) { - return [...unassignUser]; - } - - return this.#customSort([...this.#dossier.memberIds, ...unassignUser]); + const cannotAssignUser = !this.permissionsService.canAssignUser(this.data.files, this.#dossier, this.data.targetStatus); + if (cannotAssignUser) { + return [...unassignUser]; } - if (this.#dossier.approverIds.length > 1 && cannotAssignUser) { - return [...unassignUser]; + if (this.mode === 'reviewer') { + return this.#customSort([...this.#dossier.memberIds, ...unassignUser]); } return this.#customSort([...this.#dossier.approverIds, ...unassignUser]); @@ -97,12 +95,22 @@ export class AssignReviewerApproverDialogComponent { return null; } + const files = this.data.files; + const hasOnlyOneFile = this.hasOnlyOneFile(files); if (this.mode === 'reviewer') { - return this.data.files.length === 1 ? this.data.files[0].assignee : this.currentUser.id; + if (!hasOnlyOneFile) { + return this.currentUser.id; + } + + if (files[0].assignee) { + return files[0].assignee; + } + + return this.data.withCurrentUserAsDefault ? this.currentUser.id : undefined; } - const assignee = this.data.files[0].assignee; - if (this.data.files.length === 1 && this.permissionsService.isApprover(this.#dossier, assignee)) { + const assignee = files[0].assignee; + if (hasOnlyOneFile && this.permissionsService.isApprover(this.#dossier, assignee)) { return assignee; } @@ -117,6 +125,10 @@ export class AssignReviewerApproverDialogComponent { }); } + hasOnlyOneFile(files: File[]): files is [File] { + return files.length === 1; + } + async save() { this._loadingService.start(); const selectedUser = this.selectedUser; @@ -134,7 +146,7 @@ export class AssignReviewerApproverDialogComponent { } this._loadingService.stop(); - this._dialogRef.close(true); + this.close(true); } #customSort(ids: string[]) { diff --git a/apps/red-ui/src/app/modules/shared-dossiers/services/dossiers-dialog.service.ts b/apps/red-ui/src/app/modules/shared-dossiers/services/dossiers-dialog.service.ts index 4ec625777..d5b189438 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/services/dossiers-dialog.service.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/services/dossiers-dialog.service.ts @@ -1,11 +1,10 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { EditDossierDialogComponent } from '../dialogs/edit-dossier-dialog/edit-dossier-dialog.component'; -import { AssignReviewerApproverDialogComponent } from '../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component'; import { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui'; import { ImportRedactionsDialogComponent } from '../../file-preview/dialogs/import-redactions-dialog/import-redactions-dialog'; +import { EditDossierDialogComponent } from '../dialogs/edit-dossier-dialog/edit-dossier-dialog.component'; -type DialogType = 'confirm' | 'editDossier' | 'assignFile' | 'importRedactions'; +type DialogType = 'confirm' | 'editDossier' | 'importRedactions'; @Injectable({ providedIn: 'root', @@ -20,10 +19,6 @@ export class DossiersDialogService extends DialogService { component: EditDossierDialogComponent, dialogConfig: { ...largeDialogConfig }, }, - assignFile: { - component: AssignReviewerApproverDialogComponent, - dialogConfig: { disableClose: false }, - }, importRedactions: { component: ImportRedactionsDialogComponent, dialogConfig: { disableClose: false }, diff --git a/apps/red-ui/src/app/modules/shared-dossiers/services/file-assign.service.ts b/apps/red-ui/src/app/modules/shared-dossiers/services/file-assign.service.ts index e661a3b6a..d0c506b9a 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/services/file-assign.service.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/services/file-assign.service.ts @@ -1,12 +1,13 @@ import { Injectable } from '@angular/core'; -import { Dossier, File, User, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; -import { DossiersDialogService } from './dossiers-dialog.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { FilesService } from '@services/files/files.service'; -import { IConfirmationDialogData, LoadingService, Toaster } from '@iqser/common-ui'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { firstValueFrom } from 'rxjs'; +import { IConfirmationDialogData, IqserDialog, LoadingService, Toaster } from '@iqser/common-ui'; import { getCurrentUser } from '@iqser/common-ui/lib/users'; +import { Dossier, File, User, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; +import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; +import { FilesService } from '@services/files/files.service'; +import { firstValueFrom } from 'rxjs'; +import { AssignReviewerApproverDialogComponent } from '../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component'; +import { DossiersDialogService } from './dossiers-dialog.service'; const atLeastOneAssignee = (files: File[]) => files.reduce((acc, fs) => acc || !!fs.assignee, false); @@ -19,6 +20,7 @@ export class FileAssignService { private readonly _filesService: FilesService, private readonly _loadingService: LoadingService, private readonly _dialogService: DossiersDialogService, + private readonly _iqserDialog: IqserDialog, private readonly _activeDossiersService: ActiveDossiersService, ) {} @@ -26,7 +28,7 @@ export class FileAssignService { const assignReq = async () => { this._loadingService.start(); if (files[0].isNew) { - await this._makeAssignFileRequest(this.currentUser.id, 'UNDER_REVIEW', files); + await this.#makeAssignFileRequest(this.currentUser.id, 'UNDER_REVIEW', files); } else { await this._filesService.setAssignee(files, this.currentUser.id); } @@ -49,40 +51,46 @@ export class FileAssignService { } async assignReviewer(file: File, ignoreChanged = false): Promise { - await this._assignFile(WorkflowFileStatuses.UNDER_REVIEW, file, ignoreChanged); + await this.#assignFile(WorkflowFileStatuses.UNDER_REVIEW, file, ignoreChanged); } async assignApprover(file: File, ignoreChanged = false): Promise { - await this._assignFile(WorkflowFileStatuses.UNDER_APPROVAL, file, ignoreChanged); + await this.#assignFile(WorkflowFileStatuses.UNDER_APPROVAL, file, ignoreChanged); } - private async _assignFile(targetStatus: WorkflowFileStatus, file: File, ignoreChanged = false): Promise { + async #assignFile(targetStatus: WorkflowFileStatus, file: File, ignoreChanged = false): Promise { const currentUserId = this.currentUser.id; const currentDossier = this._activeDossiersService.find(file.dossierId); - const eligibleUsersIds = this._getUserIds(targetStatus, currentDossier); + const eligibleUsersIds = this.#getUserIds(targetStatus, currentDossier); + let userId: string; if (file.isNew) { - await this._makeAssignFileRequest(currentUserId, targetStatus, [file]); - } else if (file.assignee === currentUserId) { - if (eligibleUsersIds.includes(currentUserId)) { - await this._makeAssignFileRequest(currentUserId, targetStatus, [file]); - } else if (eligibleUsersIds.length === 1) { - await this._makeAssignFileRequest(eligibleUsersIds[0], targetStatus, [file]); - } else { - const data = { targetStatus, files: [file], ignoreChanged }; - this._dialogService.openDialog('assignFile', data); - } - } else { - if (eligibleUsersIds.length === 1) { - await this._makeAssignFileRequest(eligibleUsersIds[0], targetStatus, [file]); - } else { - const data = { targetStatus, files: [file], ignoreChanged, withCurrentUserAsDefault: true }; - this._dialogService.openDialog('assignFile', data); - } + userId = currentUserId; } + + if (file.assignee === currentUserId) { + if (eligibleUsersIds.includes(currentUserId)) { + userId = currentUserId; + } + } else if (eligibleUsersIds.length === 1) { + userId = eligibleUsersIds[0]; + } + + if (userId) { + return this.#makeAssignFileRequest(userId, targetStatus, [file]); + } + + this._iqserDialog.openDefault(AssignReviewerApproverDialogComponent, { + data: { + targetStatus, + files: [file], + ignoreChanged, + withCurrentUserAsDefault: file.assignee !== currentUserId, + }, + }); } - private async _makeAssignFileRequest(userId: string, targetStatus: WorkflowFileStatus, files: File[]) { + async #makeAssignFileRequest(userId: string, targetStatus: WorkflowFileStatus, files: File[]) { this._loadingService.start(); try { @@ -100,7 +108,7 @@ export class FileAssignService { this._loadingService.stop(); } - private _getUserIds(targetStatus: WorkflowFileStatus, dossier: Dossier) { + #getUserIds(targetStatus: WorkflowFileStatus, dossier: Dossier) { return targetStatus === WorkflowFileStatuses.UNDER_APPROVAL || targetStatus === WorkflowFileStatuses.APPROVED ? dossier.approverIds : dossier.memberIds; diff --git a/apps/red-ui/src/app/services/files/files.service.ts b/apps/red-ui/src/app/services/files/files.service.ts index 36244ec74..ae2857d51 100644 --- a/apps/red-ui/src/app/services/files/files.service.ts +++ b/apps/red-ui/src/app/services/files/files.service.ts @@ -1,13 +1,13 @@ import { Injectable } from '@angular/core'; import { EntitiesService, isArray, QueryParam } from '@iqser/common-ui'; +import { List, mapEach } from '@iqser/common-ui/lib/utils'; import { File, IFile } from '@red/domain'; -import { firstValueFrom } from 'rxjs'; import { UserService } from '@users/user.service'; -import { FilesMapService } from './files-map.service'; +import { NGXLogger } from 'ngx-logger'; +import { firstValueFrom } from 'rxjs'; import { map, switchMap, tap } from 'rxjs/operators'; import { DossierStatsService } from '../dossiers/dossier-stats.service'; -import { NGXLogger } from 'ngx-logger'; -import { List, mapEach } from '@iqser/common-ui/lib/utils'; +import { FilesMapService } from './files-map.service'; const asList = (value: T | List): List => (isArray(value) ? value : [value]); @@ -73,7 +73,7 @@ export class FilesService extends EntitiesService { return this.#makePost(_files, `${this._defaultModelPath}/approved/${_files[0].dossierId}/bulk`); } - async setUnderReviewFor(files: File | List) { + async setUnderReview(files: File | List) { const _files = asList(files); return this.#makePost(_files, `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`); } diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index 62c0aacf0..4f3d6ba9b 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -10,6 +10,8 @@ import { IComment, IDossier, ProcessingFileStatuses, + WorkflowFileStatus, + WorkflowFileStatuses, } from '@red/domain'; import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service'; import { FeaturesService } from '@services/features.service'; @@ -179,11 +181,11 @@ export class PermissionsService { ); } - canAssignUser(file: File | File[], dossier: Dossier): boolean { + canAssignUser(file: File | File[], dossier: Dossier, nextStatus?: WorkflowFileStatus): boolean { const files = file instanceof File ? [file] : file; return ( this._iqserPermissionsService.has(Roles.setReviewer) && - files.reduce((acc, _file) => this.#canAssignUser(_file, dossier) && acc, true) + files.reduce((acc, _file) => this.#canAssignUser(_file, dossier, nextStatus) && acc, true) ); } @@ -468,30 +470,48 @@ export class PermissionsService { return dossier.isActive && fileCanBeSetUnderApproval && this.isApprover(dossier); } - #assignmentPrecondition(file: File, dossier: Dossier): boolean { + #fileIsOk(file: File, dossier: Dossier) { return dossier.isActive && !file.isError && !file.isProcessing && !!file.lastProcessed; } #canAssignToSelf(file: File, dossier: Dossier): boolean { - const precondition = this.#assignmentPrecondition(file, dossier) && !this.isFileAssignee(file); + const precondition = this.#fileIsOk(file, dossier) && !this.isFileAssignee(file); return precondition && (this.isApprover(dossier) || (this.isDossierMember(dossier) && (file.isNew || file.isUnderReview))); } - #canAssignUser(file: File, dossier: Dossier) { - const precondition = this.#assignmentPrecondition(file, dossier); - - if (precondition) { - if ((file.isNew || file.isUnderReview) && dossier.hasReviewers && this.isDossierMember(dossier)) { - return true; - } - if ((file.isUnderApproval || file.isApproved) && dossier.approverIds.length > 1 && this.isApprover(dossier)) { - return true; - } + #canAssignUser(file: File, dossier: Dossier, nextStatus?: WorkflowFileStatus) { + if (!this.#fileIsOk(file, dossier)) { + return false; } - return false; + + if (nextStatus === WorkflowFileStatuses.UNDER_REVIEW) { + return this.#canAssignReviewer(file, dossier); + } + + if (nextStatus === WorkflowFileStatuses.UNDER_APPROVAL || nextStatus === WorkflowFileStatuses.APPROVED) { + return this.#canAssignApprover(file, dossier); + } + + if ((file.isNew || file.isUnderReview) && this.isDossierMember(dossier)) { + return true; + } + + if ((file.isUnderApproval || file.isApproved) && dossier.approverIds.length > 1 && this.isApprover(dossier)) { + return true; + } + } + + #canAssignReviewer(file: File, dossier: Dossier) { + const fileStatesForReviewer = file.isNew || file.isUnderReview || file.isUnderApproval; + return fileStatesForReviewer && this.isDossierMember(dossier); + } + + #canAssignApprover(file: File, dossier: Dossier) { + const fileStatesForApprover = file.isUnderReview || file.isUnderApproval || file.isApproved; + return fileStatesForApprover && dossier.hasApprovers && this.isApprover(dossier); } #canUnassignUser(file: File, dossier: Dossier) { - return this.#assignmentPrecondition(file, dossier) && !!file.assignee && this.isAssigneeOrApprover(file, dossier); + return this.#fileIsOk(file, dossier) && !!file.assignee && this.isAssigneeOrApprover(file, dossier); } } diff --git a/libs/common-ui b/libs/common-ui index 8d4a68f92..2f2ee530b 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 8d4a68f92cc74c54382ff898c39fab0cd8e0f72c +Subproject commit 2f2ee530b15e9e84dc28c3a23340cfb8b8b94319 diff --git a/libs/red-domain/src/lib/dossiers/dossier.model.ts b/libs/red-domain/src/lib/dossiers/dossier.model.ts index 32a2f8ab2..a4efaf53a 100644 --- a/libs/red-domain/src/lib/dossiers/dossier.model.ts +++ b/libs/red-domain/src/lib/dossiers/dossier.model.ts @@ -23,7 +23,7 @@ export class Dossier implements IDossier, IListable { readonly watermarkId?: number; readonly previewWatermarkId?: number; readonly archivedTime: string; - readonly hasReviewers: boolean; + readonly hasApprovers: boolean; readonly routerLink: string; readonly dossiersListRouterLink: string; readonly id: string; @@ -51,7 +51,7 @@ export class Dossier implements IDossier, IListable { this.watermarkId = dossier.watermarkId; this.previewWatermarkId = dossier.previewWatermarkId; this.archivedTime = dossier.archivedTime; - this.hasReviewers = !!this.memberIds && this.memberIds.length > 1; + this.hasApprovers = !!this.approverIds && this.approverIds.length > 1; this.isSoftDeleted = this.softDeletedTime !== null; this.isArchived = this.archivedTime !== null;