diff --git a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.spec.ts b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.spec.ts index aa52592b1..fafc9e876 100644 --- a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.spec.ts +++ b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.spec.ts @@ -252,7 +252,7 @@ describe('WorkflowEditDialogComponent', () => { expect(component.object.actions.length).toEqual(2) }) - it('should update order and remove ids from actions on drag n drop', () => { + it('should update order on drag n drop', () => { const action1 = workflow.actions[0] const action2 = workflow.actions[1] component.object = workflow @@ -261,8 +261,6 @@ describe('WorkflowEditDialogComponent', () => { WorkflowAction[] >) expect(component.object.actions).toEqual([action2, action1]) - expect(action1.id).toBeNull() - expect(action2.id).toBeNull() }) it('should not include auto matching in algorithms', () => { 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 f6d9e60f5..74221e3f0 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 @@ -1283,11 +1283,6 @@ export class WorkflowEditDialogComponent const actionField = this.actionFields.at(event.previousIndex) this.actionFields.removeAt(event.previousIndex) this.actionFields.insert(event.currentIndex, actionField) - // removing id will effectively re-create the actions in this order - this.object.actions.forEach((a) => (a.id = null)) - this.actionFields.controls.forEach((c) => - c.get('id').setValue(null, { emitEvent: false }) - ) } save(): void { diff --git a/src/documents/migrations/1076_workflowaction_order.py b/src/documents/migrations/1076_workflowaction_order.py new file mode 100644 index 000000000..5c9f7ff52 --- /dev/null +++ b/src/documents/migrations/1076_workflowaction_order.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.7 on 2026-01-14 16:53 + +from django.db import migrations +from django.db import models +from django.db.models import F + + +def populate_action_order(apps, schema_editor): + WorkflowAction = apps.get_model("documents", "WorkflowAction") + WorkflowAction.objects.all().update(order=F("id")) + + +class Migration(migrations.Migration): + dependencies = [ + ("documents", "1075_alter_paperlesstask_task_name"), + ] + + operations = [ + migrations.AddField( + model_name="workflowaction", + name="order", + field=models.PositiveIntegerField(default=0, verbose_name="order"), + ), + migrations.RunPython( + populate_action_order, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index 0bf3d48dd..372fafaf2 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -1295,6 +1295,8 @@ class WorkflowAction(models.Model): default=WorkflowActionType.ASSIGNMENT, ) + order = models.PositiveIntegerField(_("order"), default=0) + assign_title = models.TextField( _("assign title"), null=True, diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index b780db815..a265b036b 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -2577,7 +2577,8 @@ class WorkflowSerializer(serializers.ModelSerializer): set_triggers.append(trigger_instance) if actions is not None and actions is not serializers.empty: - for action in actions: + for index, action in enumerate(actions): + action["order"] = index assign_tags = action.pop("assign_tags", None) assign_view_users = action.pop("assign_view_users", None) assign_view_groups = action.pop("assign_view_groups", None) @@ -2704,6 +2705,16 @@ class WorkflowSerializer(serializers.ModelSerializer): return instance + def to_representation(self, instance): + data = super().to_representation(instance) + actions = instance.actions.order_by("order", "pk") + data["actions"] = WorkflowActionSerializer( + actions, + many=True, + context=self.context, + ).data + return data + class TrashSerializer(SerializerWithPerms): documents = serializers.ListField( diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py index bff68780b..cfd2f185b 100644 --- a/src/documents/signals/handlers.py +++ b/src/documents/signals/handlers.py @@ -781,7 +781,7 @@ def run_workflows( if matching.document_matches_workflow(document, workflow, trigger_type): action: WorkflowAction - for action in workflow.actions.all(): + for action in workflow.actions.order_by("order", "pk"): message = f"Applying {action} from {workflow}" if not use_overrides: logger.info(message, extra={"group": logging_group}) diff --git a/src/documents/workflows/utils.py b/src/documents/workflows/utils.py index 553622252..0a644b0eb 100644 --- a/src/documents/workflows/utils.py +++ b/src/documents/workflows/utils.py @@ -20,9 +20,6 @@ def get_workflows_for_trigger( wrap it in a list; otherwise fetch enabled workflows for the trigger with the prefetches used by the runner. """ - if workflow_to_run is not None: - return [workflow_to_run] - annotated_actions = ( WorkflowAction.objects.select_related( "assign_correspondent", @@ -105,10 +102,25 @@ def get_workflows_for_trigger( ) ) + action_prefetch = Prefetch( + "actions", + queryset=annotated_actions.order_by("order", "pk"), + ) + + if workflow_to_run is not None: + return ( + Workflow.objects.filter(pk=workflow_to_run.pk) + .prefetch_related( + action_prefetch, + "triggers", + ) + .distinct() + ) + return ( Workflow.objects.filter(enabled=True, triggers__type=trigger_type) .prefetch_related( - Prefetch("actions", queryset=annotated_actions), + action_prefetch, "triggers", ) .order_by("order")