diff --git a/apps/red-ui/src/app/modules/admin/dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component.ts index c26e9b629..cdd4760e2 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component.ts @@ -28,10 +28,15 @@ export class CloneDossierTemplateDialogComponent { async save() { this._loadingService.start(); try { - await firstValueFrom(this._dossierTemplatesService.clone(this.dossierTemplateId, this.nameOfClonedDossierTemplate)); + await firstValueFrom( + this._dossierTemplatesService.clone(this.dossierTemplateId, { + ...this._dossierTemplate, + name: this.nameOfClonedDossierTemplate, + }), + ); this._dialogRef.close(true); } catch (error: any) { - this._toaster.error(_('clone-dossier-template.error.generic'), { error }); + this._toaster.error(_('clone-dossier-template.error.generic'), { params: error }); } this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.ts index 5d54fbe82..5e82b462b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.ts @@ -68,8 +68,8 @@ export class ReportsScreenComponent implements OnInit { ); this._loadingService.stop(); download(data, template.fileName); - } catch (e) { - this._toaster.error(_('error.http.generic')); + } catch (error) { + this._toaster.error(_('error.http.generic'), { params: error }); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/dashboard/dashboard.module.ts b/apps/red-ui/src/app/modules/dashboard/dashboard.module.ts index 3acd8ffa0..6e5752ec9 100644 --- a/apps/red-ui/src/app/modules/dashboard/dashboard.module.ts +++ b/apps/red-ui/src/app/modules/dashboard/dashboard.module.ts @@ -5,6 +5,7 @@ import { RouterModule } from '@angular/router'; import { SharedModule } from '../shared/shared.module'; import { TemplateStatsComponent } from './components/template-stats/template-stats.component'; import { BreadcrumbTypes } from '@red/domain'; +import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module'; const routes = [ { @@ -18,6 +19,6 @@ const routes = [ @NgModule({ declarations: [DashboardScreenComponent, TemplateStatsComponent], - imports: [RouterModule.forChild(routes), CommonModule, SharedModule], + imports: [RouterModule.forChild(routes), CommonModule, SharedModule, SharedDossiersModule], }) export class DashboardModule {} 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 016914ee6..54a03d3d6 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 @@ -170,8 +170,9 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { true, ); const allFilesAreUnderApproval = this.selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true); + const allFilesAreApproved = this.selectedFiles.reduce((acc, file) => acc && file.isApproved, true); this.#allFilesAreExcluded = this.selectedFiles.reduce((acc, file) => acc && file.excluded, true); - this.#canMoveToSameState = allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval; + this.#canMoveToSameState = allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval || allFilesAreApproved; this.#canAssign = this.#canMoveToSameState && 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 6528bc387..343e61cd6 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,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Dossier, File } from '@red/domain'; +import { Dossier, File, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; import { DossiersDialogService } from '../../shared-dossiers/services/dossiers-dialog.service'; import { ConfirmationDialogInput, LoadingService } from '@iqser/common-ui'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; @@ -26,7 +26,7 @@ export class BulkActionsService { 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, 'approver', true); + this._assignFiles(files, WorkflowFileStatuses.UNDER_APPROVAL, true); } else { this._loadingService.start(); await firstValueFrom(this._filesService.setUnderApprovalFor(files, dossier.id, dossier.approverIds[0])); @@ -124,15 +124,15 @@ export class BulkActionsService { } } - assign(files: File[], mode: 'reviewer' | 'approver' = files[0].isUnderApproval ? 'approver' : 'reviewer') { - this._assignFiles(files, mode, false, true); + assign(files: File[]): void { + this._assignFiles(files, files[0].workflowStatus, false, true); } private _getDossier(files: File[]): Dossier { return this._activeDossiersService.find(files[0].dossierId); } - private _assignFiles(files: File[], mode: 'reviewer' | 'approver', ignoreChanged = false, withUnassignedOption = false) { - this._dialogService.openDialog('assignFile', null, { mode, files, ignoreChanged, withUnassignedOption }); + private _assignFiles(files: File[], targetStatus: WorkflowFileStatus, ignoreChanged = false, withUnassignedOption = false): void { + this._dialogService.openDialog('assignFile', null, { targetStatus, files, ignoreChanged, withUnassignedOption }); } } 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 e78e99557..ee4db7c40 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 @@ -102,11 +102,11 @@ export class UserManagementComponent { const { dossierId, filename } = file; this.loadingService.start(); - if (!assigneeId) { - await firstValueFrom(this.filesService.setUnassigned([file], dossierId)); + if (!assigneeId || file.isApproved) { + await firstValueFrom(this.filesService.setAssignee([file], dossierId, assigneeId)); } else if (file.isNew || file.isUnderReview) { await firstValueFrom(this.filesService.setReviewerFor([file], dossierId, assigneeId)); - } else if (file.isUnderApproval) { + } else { await firstValueFrom(this.filesService.setUnderApprovalFor([file], dossierId, assigneeId)); } 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 3c12d7f43..4f181ef51 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 @@ -319,11 +319,11 @@ export class FileActionsComponent implements OnChanges { } private _assign($event: MouseEvent) { - const mode = this.file.isUnderApproval ? 'approver' : 'reviewer'; const files = [this.file]; + const targetStatus = this.file.workflowStatus; const withCurrentUserAsDefault = true; const withUnassignedOption = true; - this._dialogService.openDialog('assignFile', $event, { mode, files, withCurrentUserAsDefault, withUnassignedOption }); + this._dialogService.openDialog('assignFile', $event, { targetStatus, files, withCurrentUserAsDefault, withUnassignedOption }); } private async _assignToMe($event: MouseEvent) { diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.html b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.html index 1e851ab46..0508326d7 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.html +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.html @@ -1,17 +1,11 @@
-
+
- {{ 'assign-owner.dialog.label' | translate: { type: data.mode } }} + {{ 'assign-owner.dialog.label' | translate: { type: mode } }} {{ userId | name: { defaultValue: 'initials-avatar.unassigned' | translate } }} 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 5751f277c..e48a81f16 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 @@ -3,7 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { UserService } from '@services/user.service'; import { LoadingService, Toaster } from '@iqser/common-ui'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Dossier, File } from '@red/domain'; +import { Dossier, File, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { FilesService } from '@services/files/files.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; @@ -11,7 +11,7 @@ import { PermissionsService } from '@services/permissions.service'; import { firstValueFrom } from 'rxjs'; class DialogData { - mode: 'approver' | 'reviewer'; + targetStatus: WorkflowFileStatus; files: File[]; ignoreChanged?: boolean; withUnassignedOption?: boolean; @@ -24,6 +24,7 @@ class DialogData { }) export class AssignReviewerApproverDialogComponent { readonly form: FormGroup; + readonly mode: 'reviewer' | 'approver'; dossier: Dossier; constructor( @@ -39,6 +40,10 @@ export class AssignReviewerApproverDialogComponent { ) { this.dossier = this._activeDossiersService.find(this.data.files[0].dossierId); this.form = this._getForm(); + this.mode = + data.targetStatus === WorkflowFileStatuses.UNDER_APPROVAL || data.targetStatus === WorkflowFileStatuses.APPROVED + ? 'approver' + : 'reviewer'; } get selectedUser(): string { @@ -47,9 +52,10 @@ export class AssignReviewerApproverDialogComponent { get userOptions() { const unassignUser = this._canUnassignFiles && this.data.withUnassignedOption ? [undefined] : []; - return this.data.mode === 'approver' - ? [...this.dossier.approverIds, ...unassignUser] - : [...this.dossier.memberIds, ...unassignUser]; + if (!this.permissionsService.canAssignUser(this.data.files, this.dossier)) { + return [...unassignUser]; + } + return this.mode === 'reviewer' ? [...this.dossier.memberIds, ...unassignUser] : [...this.dossier.approverIds, ...unassignUser]; } get changed(): boolean { @@ -97,9 +103,9 @@ export class AssignReviewerApproverDialogComponent { async save() { this._loadingService.start(); try { - if (!this.selectedUser) { - await firstValueFrom(this._filesService.setUnassigned(this.data.files, this.dossier.id)); - } else if (this.data.mode === 'reviewer') { + if (!this.selectedUser || this.data.targetStatus === WorkflowFileStatuses.APPROVED) { + await firstValueFrom(this._filesService.setAssignee(this.data.files, this.dossier.id, this.selectedUser)); + } else if (this.mode === 'reviewer') { await firstValueFrom(this._filesService.setReviewerFor(this.data.files, this.dossier.id, this.selectedUser)); } else { await firstValueFrom(this._filesService.setUnderApprovalFor(this.data.files, this.dossier.id, this.selectedUser)); 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 82269a1f3..c16888d05 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,6 +1,6 @@ import { Injectable } from '@angular/core'; import { UserService } from '@services/user.service'; -import { Dossier, File, User } from '@red/domain'; +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'; @@ -8,16 +8,6 @@ import { ConfirmationDialogInput, LoadingService, Toaster } from '@iqser/common- import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { firstValueFrom } from 'rxjs'; -const changeReviewerDialogInput = new ConfirmationDialogInput({ - title: _('confirmation-dialog.assign-file-to-me.title'), - question: _('confirmation-dialog.assign-file-to-me.question'), -}); - -const changeApproverDialogInput = new ConfirmationDialogInput({ - title: _('confirmation-dialog.assign-me-as-approver.title'), - question: _('confirmation-dialog.assign-me-as-approver.question'), -}); - const atLeastOneAssignee = (files: File[]) => files.reduce((acc, fs) => acc || !!fs.assignee, false); @Injectable() @@ -35,76 +25,66 @@ export class FileAssignService { this.currentUser = userService.currentUser; } - async assignToMe(files: File[]) { - const filesAreUnderApproval = files.reduce((acc, fs) => acc && fs.isUnderApproval, true); + async assignToMe(files: File[]): Promise { + const assignReq = async () => { + this._loadingService.start(); + await firstValueFrom(this._filesService.setAssignee(files, files[0].dossierId, this.currentUser.id)); + this._loadingService.stop(); + }; + if (atLeastOneAssignee(files)) { + const dialogInput = new ConfirmationDialogInput({ + title: _('confirmation-dialog.assign-file-to-me.title'), + question: _('confirmation-dialog.assign-file-to-me.question'), + }); + const ref = this._dialogService.openDialog('confirm', null, dialogInput, assignReq); + return firstValueFrom(ref.afterClosed()); + } - return filesAreUnderApproval ? this.#assignMeAsApprover(files) : this.#assignMeAsReviewer(files); + return assignReq(); } async assignReviewer($event: MouseEvent, file: File, ignoreChanged = false): Promise { - await this._assignFile('reviewer', $event, file, ignoreChanged); + await this._assignFile(WorkflowFileStatuses.UNDER_REVIEW, $event, file, ignoreChanged); } async assignApprover($event: MouseEvent, file: File, ignoreChanged = false): Promise { - await this._assignFile('approver', $event, file, ignoreChanged); + await this._assignFile(WorkflowFileStatuses.UNDER_APPROVAL, $event, file, ignoreChanged); } - #assignMeAsReviewer(files: File[]) { - if (atLeastOneAssignee(files)) { - const cb = () => this.#assignReviewerToCurrentUser(files); - const ref = this._dialogService.openDialog('confirm', null, changeReviewerDialogInput, cb); - - return firstValueFrom(ref.afterClosed()); - } - - return this.#assignReviewerToCurrentUser(files); - } - - #assignMeAsApprover(files: File[]) { - if (atLeastOneAssignee(files)) { - const cb = () => this.#assignApproverToCurrentUser(files); - const ref = this._dialogService.openDialog('confirm', null, changeApproverDialogInput, cb); - - return firstValueFrom(ref.afterClosed()); - } - - return this.#assignApproverToCurrentUser(files); - } - - private async _assignFile(mode: 'reviewer' | 'approver', $event: MouseEvent, file: File, ignoreChanged = false): Promise { + private async _assignFile(targetStatus: WorkflowFileStatus, $event: MouseEvent, file: File, ignoreChanged = false): Promise { $event?.stopPropagation(); const currentUserId = this.currentUser.id; const currentDossier = this._activeDossiersService.find(file.dossierId); - const eligibleUsersIds = this._getUserIds(mode, currentDossier); + const eligibleUsersIds = this._getUserIds(targetStatus, currentDossier); if (file.isNew) { - await this._makeAssignFileRequest(currentUserId, mode, [file]); + await this._makeAssignFileRequest(currentUserId, targetStatus, [file]); } else if (file.assignee === currentUserId) { if (eligibleUsersIds.includes(currentUserId)) { - await this._makeAssignFileRequest(currentUserId, mode, [file]); + await this._makeAssignFileRequest(currentUserId, targetStatus, [file]); } else if (eligibleUsersIds.length === 1) { - await this._makeAssignFileRequest(eligibleUsersIds[0], mode, [file]); + await this._makeAssignFileRequest(eligibleUsersIds[0], targetStatus, [file]); } else { - const data = { mode, files: [file], ignoreChanged }; + const data = { targetStatus, files: [file], ignoreChanged }; this._dialogService.openDialog('assignFile', null, data); } } else { if (eligibleUsersIds.length === 1) { - await this._makeAssignFileRequest(eligibleUsersIds[0], mode, [file]); + await this._makeAssignFileRequest(eligibleUsersIds[0], targetStatus, [file]); } else { - const data = { mode, files: [file], ignoreChanged, withCurrentUserAsDefault: true }; + const data = { targetStatus, files: [file], ignoreChanged, withCurrentUserAsDefault: true }; this._dialogService.openDialog('assignFile', null, data); } } } - private async _makeAssignFileRequest(userId: string, mode: 'reviewer' | 'approver', files: File[]) { + private async _makeAssignFileRequest(userId: string, targetStatus: WorkflowFileStatus, files: File[]) { this._loadingService.start(); try { - if (!userId) { - await firstValueFrom(this._filesService.setUnassigned(files, files[0].dossierId)); - } else if (mode === 'reviewer') { + if (!userId || targetStatus === WorkflowFileStatuses.APPROVED) { + await firstValueFrom(this._filesService.setAssignee(files, files[0].dossierId, userId)); + } else if (targetStatus === WorkflowFileStatuses.UNDER_REVIEW) { await firstValueFrom(this._filesService.setReviewerFor(files, files[0].dossierId, userId)); } else { await firstValueFrom(this._filesService.setUnderApprovalFor(files, files[0].dossierId, userId)); @@ -115,21 +95,9 @@ export class FileAssignService { this._loadingService.stop(); } - private _getUserIds(mode: 'reviewer' | 'approver', dossier: Dossier) { - return mode === 'approver' ? dossier.approverIds : dossier.memberIds; - } - - async #assignReviewerToCurrentUser(files: File[]) { - this._loadingService.start(); - const reviewer$ = this._filesService.setReviewerFor(files, files[0].dossierId, this.currentUser.id); - await firstValueFrom(reviewer$); - this._loadingService.stop(); - } - - async #assignApproverToCurrentUser(files: File[]) { - this._loadingService.start(); - const approver$ = this._filesService.setUnderApprovalFor(files, files[0].dossierId, this.currentUser.id); - await firstValueFrom(approver$); - this._loadingService.stop(); + private _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/breadcrumbs.service.ts b/apps/red-ui/src/app/services/breadcrumbs.service.ts index f4d640ff6..44f157203 100644 --- a/apps/red-ui/src/app/services/breadcrumbs.service.ts +++ b/apps/red-ui/src/app/services/breadcrumbs.service.ts @@ -3,13 +3,13 @@ import { List } from '@iqser/common-ui'; import { ActivatedRouteSnapshot, IsActiveMatchOptions, NavigationEnd, Router } from '@angular/router'; import { BehaviorSubject, Observable, of } from 'rxjs'; import { filter, pluck } from 'rxjs/operators'; -import { FilesMapService } from '@services/files/files-map.service'; +import { FilesMapService } from './files/files-map.service'; import { TranslateService } from '@ngx-translate/core'; import { BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, FILE_ID } from '@red/domain'; -import { DossiersService } from '@services/dossiers/dossiers.service'; -import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider'; -import { FeaturesService } from '@services/features.service'; -import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service'; +import { DossiersService } from './dossiers/dossiers.service'; +import { dossiersServiceResolver } from './entity-services/dossiers.service.provider'; +import { FeaturesService } from './features.service'; +import { DashboardStatsService } from './dossier-templates/dashboard-stats.service'; export type RouterLinkActiveOptions = { exact: boolean } | IsActiveMatchOptions; export type BreadcrumbDisplayType = 'text' | 'dropdown'; diff --git a/apps/red-ui/src/app/services/dossier-templates/dashboard-stats.service.ts b/apps/red-ui/src/app/services/dossier-templates/dashboard-stats.service.ts index 2003fe426..24a1687ef 100644 --- a/apps/red-ui/src/app/services/dossier-templates/dashboard-stats.service.ts +++ b/apps/red-ui/src/app/services/dossier-templates/dashboard-stats.service.ts @@ -4,7 +4,7 @@ import { Injectable, Injector } from '@angular/core'; import { NGXLogger } from 'ngx-logger'; import { Observable } from 'rxjs'; import { map, switchMap, tap } from 'rxjs/operators'; -import { DossierStatesService } from '@services/entity-services/dossier-states.service'; +import { DossierStatesService } from '../entity-services/dossier-states.service'; const templatesSorter = (a: DashboardStats, b: DashboardStats) => { if (a.numberOfActiveDossiers > 0 && b.numberOfActiveDossiers === 0) { diff --git a/apps/red-ui/src/app/services/dossier-templates/dossier-templates.service.ts b/apps/red-ui/src/app/services/dossier-templates/dossier-templates.service.ts index 979e3d9e5..098b375b0 100644 --- a/apps/red-ui/src/app/services/dossier-templates/dossier-templates.service.ts +++ b/apps/red-ui/src/app/services/dossier-templates/dossier-templates.service.ts @@ -70,10 +70,8 @@ export class DossierTemplatesService extends EntitiesService this.loadAll())); } - clone(dossierTemplateId: string, name: string) { - return this._post(null, `${this._defaultModelPath}/${dossierTemplateId}/clone?nameOfClonedDossierTemplate=${name}`).pipe( - switchMap(() => this.loadAll()), - ); + clone(dossierTemplateId: string, body: IDossierTemplate) { + return this._post(body, `${this._defaultModelPath}/${dossierTemplateId}/clone`).pipe(switchMap(() => this.loadAll())); } refreshDossierTemplate(dossierTemplateId: string): Observable { diff --git a/apps/red-ui/src/app/services/dossiers/archived-dossiers.service.ts b/apps/red-ui/src/app/services/dossiers/archived-dossiers.service.ts index a140a587e..9a572140b 100644 --- a/apps/red-ui/src/app/services/dossiers/archived-dossiers.service.ts +++ b/apps/red-ui/src/app/services/dossiers/archived-dossiers.service.ts @@ -6,7 +6,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { ActiveDossiersService } from './active-dossiers.service'; import { DossiersService } from './dossiers.service'; import { FilesMapService } from '../files/files-map.service'; -import { FeaturesService } from '@services/features.service'; +import { FeaturesService } from '../features.service'; @Injectable({ providedIn: 'root' }) export class ArchivedDossiersService extends DossiersService { diff --git a/apps/red-ui/src/app/services/dossiers/dossiers.service.ts b/apps/red-ui/src/app/services/dossiers/dossiers.service.ts index 2a576a40f..e7d2809ab 100644 --- a/apps/red-ui/src/app/services/dossiers/dossiers.service.ts +++ b/apps/red-ui/src/app/services/dossiers/dossiers.service.ts @@ -7,7 +7,7 @@ import { DossierStatsService } from './dossier-stats.service'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { NGXLogger } from 'ngx-logger'; -import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service'; +import { DashboardStatsService } from '../dossier-templates/dashboard-stats.service'; const CONFLICT_MSG = _('add-dossier-dialog.errors.dossier-already-exists'); const GENERIC_MSG = _('add-dossier-dialog.errors.generic'); diff --git a/apps/red-ui/src/app/services/entity-services/dossier-states.service.ts b/apps/red-ui/src/app/services/entity-services/dossier-states.service.ts index 1fa21f731..7b8cfd323 100644 --- a/apps/red-ui/src/app/services/entity-services/dossier-states.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dossier-states.service.ts @@ -2,9 +2,9 @@ import { Injectable, Injector } from '@angular/core'; import { EntitiesService, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { DossierState, IDossierState } from '@red/domain'; import { EMPTY, forkJoin, Observable, switchMap } from 'rxjs'; -import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; +import { DossierTemplatesService } from '../dossier-templates/dossier-templates.service'; import { catchError, defaultIfEmpty, tap } from 'rxjs/operators'; -import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; +import { DossierStatesMapService } from '../entity-services/dossier-states-map.service'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; diff --git a/apps/red-ui/src/app/services/entity-services/trash.service.ts b/apps/red-ui/src/app/services/entity-services/trash.service.ts index 4552f8ce8..d888d42bf 100644 --- a/apps/red-ui/src/app/services/entity-services/trash.service.ts +++ b/apps/red-ui/src/app/services/entity-services/trash.service.ts @@ -6,11 +6,11 @@ import { forkJoin, map, Observable, of } from 'rxjs'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { ConfigService } from '../config.service'; import { PermissionsService } from '../permissions.service'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { UserService } from '@services/user.service'; +import { ActiveDossiersService } from '../dossiers/active-dossiers.service'; +import { UserService } from '../user.service'; import { flatMap } from 'lodash-es'; -import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; -import { FilesService } from '@services/files/files.service'; +import { DossierStatsService } from '../dossiers/dossier-stats.service'; +import { FilesService } from '../files/files.service'; @Injectable({ providedIn: 'root', 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 b48dbe7b5..ca141e999 100644 --- a/apps/red-ui/src/app/services/files/files.service.ts +++ b/apps/red-ui/src/app/services/files/files.service.ts @@ -42,10 +42,10 @@ export class FilesService extends EntitiesService { } @Validate() - setUnassigned(@RequiredParam() files: List, @RequiredParam() dossierId: string) { + setAssignee(@RequiredParam() files: List, @RequiredParam() dossierId: string, assigneeId: string) { const url = `${this._defaultModelPath}/set-assignee/${dossierId}/bulk`; const fileIds = files.map(f => f.id); - return this._post(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId))); + return this._post(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId))); } @Validate() diff --git a/apps/red-ui/src/app/services/notifications.service.ts b/apps/red-ui/src/app/services/notifications.service.ts index f2eed9725..0066d9c0c 100644 --- a/apps/red-ui/src/app/services/notifications.service.ts +++ b/apps/red-ui/src/app/services/notifications.service.ts @@ -6,11 +6,11 @@ import { Dossier, INotification, Notification, NotificationTypes } from '@red/do import { map, switchMap, tap } from 'rxjs/operators'; import { notificationsTranslations } from '@translations/notifications-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { UserService } from '@services/user.service'; +import { UserService } from './user.service'; import dayjs from 'dayjs'; import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; import { BASE_HREF } from '../tokens'; -import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service'; +import { DossiersCacheService } from './dossiers/dossiers-cache.service'; const INCLUDE_SEEN = false; diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index f87d59f30..ca4b1f51d 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -29,7 +29,7 @@ export class PermissionsService { return this.isAdmin(); } - isReviewerOrApprover(file: File, dossier: Dossier): boolean { + isAssigneeOrApprover(file: File, dossier: Dossier): boolean { return this.isFileAssignee(file) || this.isApprover(dossier); } @@ -121,7 +121,7 @@ export class PermissionsService { canSetUnderReview(file: File | File[], dossier: Dossier): boolean { const files = file instanceof File ? [file] : file; - return this.isApprover(dossier) && files.reduce((acc, _file) => this._canSetUnderReview(_file, dossier) && acc, true); + return files.reduce((acc, _file) => this._canSetUnderReview(_file, dossier) && acc, true); } canBeApproved(file: File | File[], dossier: Dossier): boolean { @@ -129,8 +129,9 @@ export class PermissionsService { return files.reduce((acc, _file) => this._canBeApproved(_file, dossier) && acc, true); } - isReadyForApproval(files: File | File[], dossier: Dossier): boolean { - return this.canSetUnderReview(files, dossier); + isReadyForApproval(file: File | File[], dossier: Dossier): boolean { + const files = file instanceof File ? [file] : file; + return files.reduce((acc, _file) => this._isReadyForApproval(_file, dossier) && acc, true); } canSetUnderApproval(file: File | File[], dossier: Dossier): boolean { @@ -232,7 +233,7 @@ export class PermissionsService { } canAddComment(file: File, dossier: Dossier): boolean { - return (this.isFileAssignee(file) || this.isApprover(dossier)) && !file.isApproved; + return this.isAssigneeOrApprover(file, dossier) && !file.isApproved; } canExcludePages(file: File, dossier: Dossier): boolean { @@ -260,21 +261,19 @@ export class PermissionsService { } private _canSoftDeleteFile(file: File, dossier: Dossier): boolean { - return ( - dossier.isActive && (this.isApprover(dossier) || this.isFileAssignee(file) || (!file.assignee && this.isDossierMember(dossier))) - ); + return dossier.isActive && (this.isAssigneeOrApprover(file, dossier) || (!file.assignee && this.isDossierMember(dossier))); } private _canRestoreFile(file: File, dossier: Dossier): boolean { - return this.isApprover(dossier) || this.isFileAssignee(file); + return this.isAssigneeOrApprover(file, dossier); } private _canHardDeleteFile(file: File, dossier: Dossier): boolean { - return this.isApprover(dossier) || this.isFileAssignee(file) || (!file.assignee && this.isDossierMember(dossier)); + return this.isAssigneeOrApprover(file, dossier) || (!file.assignee && this.isDossierMember(dossier)); } private _canReanalyseFile(file: File, dossier: Dossier): boolean { - return dossier.isActive && this.isReviewerOrApprover(file, dossier) && file.analysisRequired; + return dossier.isActive && this.isAssigneeOrApprover(file, dossier) && file.analysisRequired; } private _canEnableAutoAnalysis(file: File, dossier: Dossier): boolean { @@ -285,32 +284,52 @@ export class PermissionsService { return dossier.isActive && !file.excludedFromAutomaticAnalysis && this.isFileAssignee(file) && !file.isApproved; } - private _canAssignToSelf(file: File, dossier: Dossier): boolean { - const precondition = - dossier.isActive && this.isDossierMember(dossier) && !this.isFileAssignee(file) && !file.isError && !file.isProcessing; - return precondition && (file.isNew || file.isUnderReview || (file.isUnderApproval && this.isApprover(dossier))); + /** UNDER_REVIEW => NEW */ + private _canSetToNew(file: File, dossier: Dossier): boolean { + return dossier.isActive && file.isUnderReview && this.isDossierMember(dossier); } + /** UNDER_REVIEW => UNDER_APPROVAL */ private _canSetUnderApproval(file: File, dossier: Dossier): boolean { - return dossier.isActive && file.isUnderReview && this.isFileAssignee(file); + return dossier.isActive && file.isUnderReview && this.isDossierMember(dossier); } - private _canUndoApproval(file: File, dossier: Dossier): boolean { - return dossier.isActive && file.isApproved && this.isFileAssignee(file); + /** UNDER_APPROVAL => UNDER_REVIEW */ + private _canSetUnderReview(file: File, dossier: Dossier): boolean { + return dossier.isActive && file.isUnderApproval && this.isAssigneeOrApprover(file, dossier); } + /** UNDER_APPROVAL => APPROVED */ private _canBeApproved(file: File, dossier: Dossier): boolean { - return dossier.isActive && file.canBeApproved && this.isFileAssignee(file); + return this._isReadyForApproval(file, dossier) && file.canBeApproved; + } + + private _isReadyForApproval(file: File, dossier: Dossier): boolean { + return dossier.isActive && file.isUnderApproval && this.isAssigneeOrApprover(file, dossier); + } + + /** APPROVED => UNDER_APPROVAL */ + private _canUndoApproval(file: File, dossier: Dossier): boolean { + return dossier.isActive && file.isApproved && this.isApprover(dossier); + } + + private _assignmentPrecondition(file: File, dossier: Dossier): boolean { + return dossier.isActive && !file.isError && !file.isProcessing; + } + + private _canAssignToSelf(file: File, dossier: Dossier): boolean { + const precondition = this._assignmentPrecondition(file, dossier) && !this.isFileAssignee(file); + return precondition && (this.isApprover(dossier) || (this.isDossierMember(dossier) && (file.isNew || file.isUnderReview))); } private _canAssignUser(file: File, dossier: Dossier) { - const precondition = dossier.isActive && !file.isProcessing && !file.isError && !file.isApproved && this.isApprover(dossier); + const precondition = this._assignmentPrecondition(file, dossier); if (precondition) { - if ((file.isNew || file.isUnderReview) && dossier.hasReviewers) { + if ((file.isNew || file.isUnderReview) && dossier.hasReviewers && this.isDossierMember(dossier)) { return true; } - if (file.isUnderApproval && dossier.approverIds.length > 1) { + if ((file.isUnderApproval || file.isApproved) && dossier.approverIds.length > 1 && this.isApprover(dossier)) { return true; } } @@ -318,14 +337,6 @@ export class PermissionsService { } private _canUnassignUser(file: File, dossier: Dossier) { - return dossier.isActive && (file.isUnderReview || file.isUnderApproval) && (this.isFileAssignee(file) || this.isApprover(dossier)); - } - - private _canSetToNew(file: File, dossier: Dossier): boolean { - return dossier.isActive && file.isUnderReview && this.isFileAssignee(file); - } - - private _canSetUnderReview(file: File, dossier: Dossier): boolean { - return dossier.isActive && file.isUnderApproval && this.isFileAssignee(file); + return this._assignmentPrecondition(file, dossier) && !!file.assignee && this.isAssigneeOrApprover(file, dossier); } } diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 322276d2f..f9a12cdad 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,7 +1,7 @@ { "ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_URL": null, - "API_URL": "https://dev-08.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", @@ -16,7 +16,7 @@ "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", "OAUTH_IDP_HINT": null, - "OAUTH_URL": "https://dev-08.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" diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json index 07f6ce126..2fd91749e 100644 --- a/apps/red-ui/src/assets/i18n/de.json +++ b/apps/red-ui/src/assets/i18n/de.json @@ -554,10 +554,6 @@ "question": "Dieses Dokument wird gerade von einer anderen Person geprüft. Möchten Sie Reviewer werden und sich selbst dem Dokument zuweisen?", "title": "Neuen Reviewer zuweisen" }, - "assign-me-as-approver": { - "question": "", - "title": "" - }, "compare-file": { "question": "Achtung!

Seitenzahl stimmt nicht überein, aktuelles Dokument hat {currentDocumentPageCount} Seite(n). Das hochgeladene Dokument hat {compareDocumentPageCount} Seite(n).

Möchten Sie fortfahren?", "title": "Vergleichen mit: {fileName}" diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 6ad5f11e2..a435ad924 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -551,13 +551,9 @@ "title": "Warning!" }, "assign-file-to-me": { - "question": "This document is currently reviewed by someone else. Do you want to become the reviewer and assign yourself to this document?", + "question": "At least one document is currently assigned to someone else. Are you sure you want to replace them and assign yourself to these documents?", "title": "Re-assign user" }, - "assign-me-as-approver": { - "question": "This document is currently under approval by someone else. Do you want to become the approver and assign yourself to this document?", - "title": "Re-assign approver" - }, "compare-file": { "question": "Warning!

Number of pages does not match, current document has {currentDocumentPageCount} page(s). Uploaded document has {compareDocumentPageCount} page(s).

Do you wish to proceed?", "title": "Compare with file: {fileName}" diff --git a/package.json b/package.json index 84d0e0e7b..cb06a9e84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redaction", - "version": "3.492.0", + "version": "3.496.0", "private": true, "license": "MIT", "scripts": { diff --git a/paligo-theme.tar.gz b/paligo-theme.tar.gz index ad8d96cfa..2a4ff7fcf 100644 Binary files a/paligo-theme.tar.gz and b/paligo-theme.tar.gz differ