diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index 00267e76d7..630a97afbe 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -49,6 +49,12 @@ Reprocess + @if (manualWorkflows().length > 0) { + + } + diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts index 26a42336a2..4c766077a5 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts @@ -1030,6 +1030,27 @@ describe('DocumentDetailComponent', () => { expect(modalCloseSpy).toHaveBeenCalled() }) + it('should run a workflow on the document, confirm and close modal', () => { + initNormally() + const bulkEditSpy = jest.spyOn(documentService, 'bulkEdit') + bulkEditSpy.mockReturnValue(of(true)) + let openModal: NgbModalRef + modalService.activeInstances.subscribe((modal) => (openModal = modal[0])) + const toastSpy = jest.spyOn(toastService, 'showInfo') + component.runWorkflow() + const modalCloseSpy = jest.spyOn(openModal, 'close') + openModal.componentInstance.workflowId = 5 + openModal.componentInstance.force = true + openModal.componentInstance.confirmClicked.next() + expect(bulkEditSpy).toHaveBeenCalledWith( + { documents: [doc.id] }, + 'run_workflow', + { workflow: 5, filter_documents: false } + ) + expect(toastSpy).toHaveBeenCalled() + expect(modalCloseSpy).toHaveBeenCalled() + }) + it('should pass remote OCR choice when reprocessing', () => { initNormally() const reprocessSpy = jest.spyOn(documentService, 'reprocessDocuments') diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index 561fc6b681..6dbf5c6ac8 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -68,6 +68,8 @@ import { StoragePath } from 'src/app/data/storage-path' import { Tag } from 'src/app/data/tag' import { SETTINGS_KEYS, SuggestionSource } from 'src/app/data/ui-settings' import { User } from 'src/app/data/user' +import { Workflow } from 'src/app/data/workflow' +import { WorkflowTriggerType } from 'src/app/data/workflow-trigger' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' @@ -93,6 +95,7 @@ import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { StoragePathService } from 'src/app/services/rest/storage-path.service' import { TagService } from 'src/app/services/rest/tag.service' import { UserService } from 'src/app/services/rest/user.service' +import { WorkflowService } from 'src/app/services/rest/workflow.service' import { SettingsService } from 'src/app/services/settings.service' import { ToastService } from 'src/app/services/toast.service' import { TrashService } from 'src/app/services/trash.service' @@ -104,6 +107,7 @@ import { DocumentDetailFieldID } from '../admin/settings/settings.component' import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' import { PasswordRemovalConfirmDialogComponent } from '../common/confirm-dialog/password-removal-confirm-dialog/password-removal-confirm-dialog.component' import { ReprocessConfirmDialogComponent } from '../common/confirm-dialog/reprocess-confirm-dialog/reprocess-confirm-dialog.component' +import { RunWorkflowConfirmDialogComponent } from '../common/confirm-dialog/run-workflow-confirm-dialog/run-workflow-confirm-dialog.component' import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component' import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component' @@ -233,6 +237,7 @@ export class DocumentDetailComponent private componentRouterService = inject(ComponentRouterService) private deviceDetectorService = inject(DeviceDetectorService) private savedViewService = inject(SavedViewService) + private readonly workflowService = inject(WorkflowService) private readonly websocketStatusService = inject(WebsocketStatusService) private readonly useNativePdfViewerSetting = this.settings.getSignal( SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER @@ -295,6 +300,7 @@ export class DocumentDetailComponent readonly correspondents = signal(undefined) readonly documentTypes = signal(undefined) readonly storagePaths = signal(undefined) + readonly manualWorkflows = signal([]) documentForm: FormGroup = new FormGroup({ title: new FormControl(''), @@ -836,6 +842,19 @@ export class DocumentDetailComponent .pipe(first(), takeUntil(this.unsubscribeNotifier)) .subscribe((result) => this.users.set(result.results)) } + if ( + this.permissionsService.currentUserCan( + PermissionAction.View, + PermissionType.Workflow + ) + ) { + this.workflowService + .list(1, 100000, null, null, { + trigger_type: WorkflowTriggerType.Manual, + }) + .pipe(first(), takeUntil(this.unsubscribeNotifier)) + .subscribe((result) => this.manualWorkflows.set(result.results)) + } this.getCustomFields() @@ -1559,6 +1578,39 @@ export class DocumentDetailComponent }) } + runWorkflow() { + const modal = this.modalService.open(RunWorkflowConfirmDialogComponent, { + backdrop: 'static', + }) + modal.componentInstance.title = $localize`Run workflow` + modal.componentInstance.message = $localize`Run a workflow on "${this.document().title}".` + modal.componentInstance.btnCaption = $localize`Run` + modal.componentInstance.workflows = this.manualWorkflows() + modal.componentInstance.confirmClicked.subscribe(() => { + modal.componentInstance.buttonsEnabled.set(false) + this.documentsService + .bulkEdit({ documents: [this.document().id] }, 'run_workflow', { + workflow: modal.componentInstance.workflowId, + filter_documents: !modal.componentInstance.force, + }) + .subscribe({ + next: () => { + this.toastService.showInfo( + $localize`Workflow will run on "${this.document().title}" in the background.` + ) + modal.close() + }, + error: (error) => { + modal.componentInstance.buttonsEnabled.set(true) + this.toastService.showError( + $localize`Error executing operation`, + error + ) + }, + }) + }) + } + private getSelectedNonLatestVersionId(): number | null { const versions = this.document()?.versions ?? [] if (!versions.length || !this.selectedVersionId()) {