Merge branch 'dan' into 'master'

RED-7421 show correct user options in assign dialog

See merge request redactmanager/red-ui!101
This commit is contained in:
Dan Percic 2023-09-21 10:15:35 +02:00
commit 2be891c6d3
14 changed files with 215 additions and 161 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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<File> imple
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
@ViewChild('fileInput', { static: true }) private readonly _fileInput: ElementRef;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
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<File> 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<File> 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<File> imple
#computeAllFilters() {
const filterGroups = this.configService.filterGroups(
this.entitiesService.all,
this._fileAttributeConfigs,
this.#fileAttributeConfigs,
this.#dossier.dossierTemplateId,
this._needsWorkFilterTemplate,
() => this.checkedRequiredFilters,

View File

@ -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<void> {
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,
});
}
}

View File

@ -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();

View File

@ -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() {

View File

@ -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<AssignReviewerApproverDialogComponent, boolean>,
@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[]) {

View File

@ -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<DialogType> {
component: EditDossierDialogComponent,
dialogConfig: { ...largeDialogConfig },
},
assignFile: {
component: AssignReviewerApproverDialogComponent,
dialogConfig: { disableClose: false },
},
importRedactions: {
component: ImportRedactionsDialogComponent,
dialogConfig: { disableClose: false },

View File

@ -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<void> {
await this._assignFile(WorkflowFileStatuses.UNDER_REVIEW, file, ignoreChanged);
await this.#assignFile(WorkflowFileStatuses.UNDER_REVIEW, file, ignoreChanged);
}
async assignApprover(file: File, ignoreChanged = false): Promise<void> {
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<void> {
async #assignFile(targetStatus: WorkflowFileStatus, file: File, ignoreChanged = false): Promise<void> {
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;

View File

@ -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 = <T>(value: T | List<T>): List<T> => (isArray(value) ? value : [value]);
@ -73,7 +73,7 @@ export class FilesService extends EntitiesService<IFile, File> {
return this.#makePost(_files, `${this._defaultModelPath}/approved/${_files[0].dossierId}/bulk`);
}
async setUnderReviewFor(files: File | List<File>) {
async setUnderReview(files: File | List<File>) {
const _files = asList(files);
return this.#makePost(_files, `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`);
}

View File

@ -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);
}
}

@ -1 +1 @@
Subproject commit 8d4a68f92cc74c54382ff898c39fab0cd8e0f72c
Subproject commit 2f2ee530b15e9e84dc28c3a23340cfb8b8b94319

View File

@ -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;