diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index fe002792b..675c53328 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -1182,19 +1182,19 @@ src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 174 + 191 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 193 + 210 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 260 + 277 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 279 + 296 src/app/components/common/input/permissions/permissions-form/permissions-form.component.html @@ -1217,19 +1217,19 @@ src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 182 + 199 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 201 + 218 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 268 + 285 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 287 + 304 src/app/components/common/input/permissions/permissions-form/permissions-form.component.html @@ -1255,11 +1255,11 @@ src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 207 + 224 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 293 + 310 src/app/components/common/input/permissions/permissions-form/permissions-form.component.html @@ -1960,6 +1960,10 @@ src/app/components/common/dates-dropdown/dates-dropdown.component.html 11 + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts + 59 + src/app/components/document-list/document-list.component.html 239 @@ -3451,6 +3455,10 @@ src/app/components/common/dates-dropdown/dates-dropdown.component.html 74 + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts + 55 + src/app/components/document-list/document-list.component.html 248 @@ -3550,7 +3558,7 @@ src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 137 + 154 @@ -3960,7 +3968,7 @@ src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 162 + 179 @@ -3978,7 +3986,7 @@ src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 163 + 180 @@ -4372,322 +4380,403 @@ 121 + + Set scheduled trigger delay and which field to use. + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 123 + + + + Delay + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 126 + + + + Delay time string such as '3months', '1y'. + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 126 + + + + Repeat + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 127 + + + + Repeat the trigger after the delay. + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 127 + + + + Relative to + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 130 + + + + Delay custom field + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 134 + + + + Custom field to use for delay. + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 134 + + Trigger for documents that match all filters specified below. src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 122 + 139 Filter filename src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 125 + 142 Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive. src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 125 + 142 Filter sources src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 127 + 144 Filter path src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 128 + 145 Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</a> src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 128 + 145 Filter mail rule src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 129 + 146 Apply to documents consumed via this mail rule. src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 129 + 146 Content matching algorithm src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 132 + 149 Content matching pattern src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 134 + 151 Has any of tags src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 143 + 160 Has correspondent src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 144 + 161 Has document type src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 145 + 162 Action type src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 155 + 172 Assign title src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 160 + 177 Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>. src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 160 + 177 Assign tags src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 161 + 178 Assign storage path src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 164 + 181 Assign custom fields src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 165 + 182 Assign owner src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 168 + 185 Assign view permissions src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 170 + 187 Assign edit permissions src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 189 + 206 Remove tags src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 216 + 233 Remove all src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 217 + 234 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 223 + 240 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 229 + 246 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 235 + 252 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 241 + 258 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 248 + 265 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 254 + 271 Remove correspondents src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 222 + 239 Remove document types src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 228 + 245 Remove storage paths src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 234 + 251 Remove custom fields src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 240 + 257 Remove owners src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 247 + 264 Remove permissions src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 253 + 270 View permissions src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 256 + 273 Edit permissions src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 275 + 292 Consume Folder src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 39 + 40 API Upload src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 43 + 44 Mail Fetch src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 47 + 48 + + + + Modified + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts + 63 + + + src/app/data/document.ts + 99 + + + + Custom Field + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts + 67 Consumption Started src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 54 + 74 Document Added src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 58 + 78 Document Updated src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 62 + 82 + + + + Scheduled + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts + 86 Assignment src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 69 + 93 Removal src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 73 + 97 Create new workflow src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 142 + 166 Edit workflow src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts - 146 + 170 @@ -8280,13 +8369,6 @@ 46 - - Modified - - src/app/data/document.ts - 99 - - Search score diff --git a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html index 0dccfa9c7..99e25e42f 100644 --- a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html +++ b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html @@ -124,13 +124,14 @@
+
- +
@if (formGroup.get('schedule_delay_field').value === 'custom_field') {
- +
}
diff --git a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts index 570756604..09296250d 100644 --- a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts +++ b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts @@ -339,6 +339,7 @@ export class WorkflowEditDialogComponent trigger.filter_has_document_type ), schedule_delay: new FormControl(trigger.schedule_delay), + schedule_is_recurring: new FormControl(trigger.schedule_is_recurring), schedule_delay_field: new FormControl(trigger.schedule_delay_field), schedule_delay_custom_field: new FormControl( trigger.schedule_delay_custom_field @@ -448,6 +449,7 @@ export class WorkflowEditDialogComponent match: '', is_insensitive: true, schedule_delay: null, + schedule_is_recurring: false, schedule_delay_field: ScheduleDelayField.Added, schedule_delay_custom_field: null, } diff --git a/src-ui/src/app/data/workflow-trigger.ts b/src-ui/src/app/data/workflow-trigger.ts index 78d5e418b..ed160e024 100644 --- a/src-ui/src/app/data/workflow-trigger.ts +++ b/src-ui/src/app/data/workflow-trigger.ts @@ -45,6 +45,8 @@ export interface WorkflowTrigger extends ObjectWithId { schedule_delay?: string + schedule_is_recurring?: boolean + schedule_delay_field?: ScheduleDelayField schedule_delay_custom_field?: number // CustomField.id diff --git a/src/documents/migrations/1056_workflowtrigger_schedule_delay_and_more.py b/src/documents/migrations/1056_workflowtrigger_schedule_delay_and_more.py index 9391fd2d9..8a4b949b9 100644 --- a/src/documents/migrations/1056_workflowtrigger_schedule_delay_and_more.py +++ b/src/documents/migrations/1056_workflowtrigger_schedule_delay_and_more.py @@ -1,6 +1,7 @@ -# Generated by Django 5.1.1 on 2024-10-23 20:54 +# Generated by Django 5.1.1 on 2024-10-24 04:03 import django.db.models.deletion +import django.utils.timezone from django.db import migrations from django.db import models @@ -49,6 +50,15 @@ class Migration(migrations.Migration): verbose_name="schedule delay field", ), ), + migrations.AddField( + model_name="workflowtrigger", + name="schedule_is_recurring", + field=models.BooleanField( + default=False, + help_text="If the schedule should be recurring.", + verbose_name="schedule is recurring", + ), + ), migrations.AlterField( model_name="workflowtrigger", name="type", @@ -63,4 +73,49 @@ class Migration(migrations.Migration): verbose_name="Workflow Trigger Type", ), ), + migrations.CreateModel( + name="WorkflowRun", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "run_at", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + verbose_name="date run", + ), + ), + ( + "document", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="workflow_runs", + to="documents.document", + verbose_name="document", + ), + ), + ( + "workflow", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="runs", + to="documents.workflow", + verbose_name="workflow", + ), + ), + ], + options={ + "verbose_name": "workflow run", + "verbose_name_plural": "workflow runs", + }, + ), ] diff --git a/src/documents/models.py b/src/documents/models.py index 006f48a56..798075d8f 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -1115,6 +1115,14 @@ class WorkflowTrigger(models.Model): ), ) + schedule_is_recurring = models.BooleanField( + _("schedule is recurring"), + default=False, + help_text=_( + "If the schedule should be recurring.", + ), + ) + schedule_delay_field = models.CharField( _("schedule delay field"), max_length=20, @@ -1383,3 +1391,33 @@ class Workflow(models.Model): def __str__(self): return f"Workflow: {self.name}" + + +class WorkflowRun(models.Model): + workflow = models.ForeignKey( + Workflow, + on_delete=models.CASCADE, + related_name="runs", + verbose_name=_("workflow"), + ) + + document = models.ForeignKey( + Document, + null=True, + on_delete=models.CASCADE, + related_name="workflow_runs", + verbose_name=_("document"), + ) + + run_at = models.DateTimeField( + _("date run"), + default=timezone.now, + db_index=True, + ) + + class Meta: + verbose_name = _("workflow run") + verbose_name_plural = _("workflow runs") + + def __str__(self): + return f"WorkflowRun {self.pk}" diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index fdb1be8a5..764d0a8aa 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -1738,10 +1738,6 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer): label="Trigger Type", ) - schedule_delay = serializers.CharField( - required=False, - ) - class Meta: model = WorkflowTrigger fields = [ @@ -1758,6 +1754,7 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer): "filter_has_correspondent", "filter_has_document_type", "schedule_delay", + "schedule_is_recurring", "schedule_delay_field", "schedule_delay_custom_field", ] diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py index 73aee2936..0a8439085 100644 --- a/src/documents/signals/handlers.py +++ b/src/documents/signals/handlers.py @@ -37,6 +37,7 @@ from documents.models import PaperlessTask from documents.models import Tag from documents.models import Workflow from documents.models import WorkflowAction +from documents.models import WorkflowRun from documents.models import WorkflowTrigger from documents.permissions import get_objects_for_user_owner_aware from documents.permissions import set_permissions_for_object @@ -915,6 +916,11 @@ def run_workflows( document.save() document.tags.set(doc_tag_ids) + WorkflowRun.objects.create( + workflow=workflow, + document=document if not use_overrides else None, + ) + if use_overrides: return overrides, "\n".join(messages) diff --git a/src/documents/tasks.py b/src/documents/tasks.py index 3c9eba7c8..5ed23b1af 100644 --- a/src/documents/tasks.py +++ b/src/documents/tasks.py @@ -38,6 +38,7 @@ from documents.models import DocumentType from documents.models import StoragePath from documents.models import Tag from documents.models import Workflow +from documents.models import WorkflowRun from documents.models import WorkflowTrigger from documents.parsers import DocumentParser from documents.parsers import get_parser_class_for_mime_type @@ -382,6 +383,26 @@ def check_scheduled_workflows(): f"Found {documents.count()} documents for trigger {trigger}", ) for document in documents: + workflow_runs = WorkflowRun.objects.filter( + document=document, + workflow=workflow, + ) + if not trigger.schedule_is_recurring and workflow_runs.exists(): + # schedule is non-recurring and the workflow has already been run + logger.debug( + f"Skipping document {document} for non-recurring workflow {workflow} as it has already been run", + ) + continue + elif ( + trigger.schedule_is_recurring + and workflow_runs.exists() + and workflow_runs.last().run_at > timezone.now() - delay_td + ): + # schedule is recurring but the last run was within the delay + logger.debug( + f"Skipping document {document} for recurring workflow {workflow} as the last run was within the delay", + ) + continue run_workflows( WorkflowTrigger.WorkflowTriggerType.SCHEDULED, document, diff --git a/src/documents/tests/test_workflows.py b/src/documents/tests/test_workflows.py index c02b44a1f..a98cb4d3b 100644 --- a/src/documents/tests/test_workflows.py +++ b/src/documents/tests/test_workflows.py @@ -1354,7 +1354,7 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase): def test_new_trigger_type_raises_exception(self): trigger = WorkflowTrigger.objects.create( - type=4, + type=99, ) action = WorkflowAction.objects.create( assign_title="Doc assign owner",