From 855669ddf93e25fe5c45420781e191a7df63884d Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 10 Aug 2026 07:38:07 -0700 Subject: [PATCH] Fix: fixes for workflow assign custom field values (#13630) --- docs/usage.md | 4 +- .../custom-fields-values.component.spec.ts | 8 +-- .../custom-fields-values.component.ts | 2 +- src/documents/serialisers.py | 7 +++ src/documents/tests/test_api_workflows.py | 9 ++++ src/documents/tests/test_workflows.py | 49 +++++++++++++++++++ src/documents/workflows/mutations.py | 3 +- 7 files changed, 75 insertions(+), 7 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index bd03c9a51..7fc245f22 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -576,7 +576,9 @@ The following workflow action types are available: - Tags, correspondent, document type and storage path - Document owner - View and / or edit permissions to users or groups -- Custom fields. Note that no value for the field will be set +- Custom fields, optionally with a value. If no value is set, the field is only added to the + document and any value it may already have is left untouched. If a value is set, it will + overwrite an existing value of that field on the document. ##### Removal {#workflow-action-removal} diff --git a/src-ui/src/app/components/common/input/custom-fields-values/custom-fields-values.component.spec.ts b/src-ui/src/app/components/common/input/custom-fields-values/custom-fields-values.component.spec.ts index 82a065452..a50511f55 100644 --- a/src-ui/src/app/components/common/input/custom-fields-values/custom-fields-values.component.spec.ts +++ b/src-ui/src/app/components/common/input/custom-fields-values/custom-fields-values.component.spec.ts @@ -52,10 +52,10 @@ describe('CustomFieldsValuesComponent', () => { }) it('should set selectedFields and map values correctly', () => { - component.value = { 1: 'value1' } - component.selectedFields = [1, 2] - expect(component.selectedFields).toEqual([1, 2]) - expect(component.value).toEqual({ 1: 'value1', 2: null }) + component.value = { 1: 'value1', 3: 0, 4: false } + component.selectedFields = [1, 2, 3, 4] + expect(component.selectedFields).toEqual([1, 2, 3, 4]) + expect(component.value).toEqual({ 1: 'value1', 2: null, 3: 0, 4: false }) }) it('should return the correct custom field by id', () => { diff --git a/src-ui/src/app/components/common/input/custom-fields-values/custom-fields-values.component.ts b/src-ui/src/app/components/common/input/custom-fields-values/custom-fields-values.component.ts index 837f43026..65a2bea8a 100644 --- a/src-ui/src/app/components/common/input/custom-fields-values/custom-fields-values.component.ts +++ b/src-ui/src/app/components/common/input/custom-fields-values/custom-fields-values.component.ts @@ -77,7 +77,7 @@ export class CustomFieldsValuesComponent extends AbstractInputComponent this._selectedFields = newFields // map the selected fields to an object with field_id as key and value as value this.value = newFields.reduce((acc, fieldId) => { - acc[fieldId] = this.value?.[fieldId] || null + acc[fieldId] = this.value?.[fieldId] ?? null return acc }, {}) this.onChange(this.value) diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 1a491c5a4..e47f42e09 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -3213,6 +3213,13 @@ class WorkflowActionSerializer(serializers.ModelSerializer[WorkflowAction]): {"assign_title": f'Invalid f-string detected: "{e.args[0]}"'}, ) + if attrs.get("assign_custom_fields_values"): + # Empty strings treated as None to avoid unexpected behavior + attrs["assign_custom_fields_values"] = { + field_id: (None if value == "" else value) + for field_id, value in attrs["assign_custom_fields_values"].items() + } + if ( "type" in attrs and attrs["type"] == WorkflowAction.WorkflowActionType.EMAIL diff --git a/src/documents/tests/test_api_workflows.py b/src/documents/tests/test_api_workflows.py index cdd3a5f40..e2689001a 100644 --- a/src/documents/tests/test_api_workflows.py +++ b/src/documents/tests/test_api_workflows.py @@ -422,6 +422,11 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase): json.dumps( { "assign_title": "", + "assign_custom_fields": [self.cf1.id, self.cf2.id], + "assign_custom_fields_values": { + str(self.cf1.id): "", + str(self.cf2.id): 0, + }, }, ), content_type="application/json", @@ -429,6 +434,10 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase): self.assertEqual(response.status_code, status.HTTP_201_CREATED) action = WorkflowAction.objects.get(id=response.data["id"]) self.assertIsNone(action.assign_title) + self.assertEqual( + action.assign_custom_fields_values, + {str(self.cf1.id): None, str(self.cf2.id): 0}, + ) response = self.client.post( self.ENDPOINT_TRIGGERS, diff --git a/src/documents/tests/test_workflows.py b/src/documents/tests/test_workflows.py index f3dace48f..574cecf4f 100644 --- a/src/documents/tests/test_workflows.py +++ b/src/documents/tests/test_workflows.py @@ -2000,6 +2000,55 @@ class TestWorkflows( r"Doc added in \w{3,}", ) # Match any 3-letter month name + def test_document_updated_workflow_existing_custom_field_empty_value(self) -> None: + """ + GIVEN: + - Existing workflow with UPDATED trigger and action that assigns a custom field + with an empty value + WHEN: + - Document is updated that already contains the field with a value + THEN: + - The existing value is left untouched, see GH #13627 + """ + trigger = WorkflowTrigger.objects.create( + type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, + filter_has_document_type=self.dt, + ) + action = WorkflowAction.objects.create() + action.assign_custom_fields.add(self.cf1) + action.assign_custom_fields_values = {self.cf1.pk: ""} + action.save() + w = Workflow.objects.create( + name="Workflow 1", + order=0, + ) + w.triggers.add(trigger) + w.actions.add(action) + w.save() + + doc = Document.objects.create( + title="sample test", + correspondent=self.c, + original_filename="sample.pdf", + ) + CustomFieldInstance.objects.create( + document=doc, + field=self.cf1, + value_text="existing value", + ) + + superuser = User.objects.create_superuser("superuser") + self.client.force_authenticate(user=superuser) + + self.client.patch( + f"/api/documents/{doc.id}/", + {"document_type": self.dt.id}, + format="json", + ) + + doc.refresh_from_db() + self.assertEqual(doc.custom_fields.get(field=self.cf1).value, "existing value") + def test_document_updated_workflow_existing_custom_field(self) -> None: """ GIVEN: diff --git a/src/documents/workflows/mutations.py b/src/documents/workflows/mutations.py index 7d33bc545..869ea240f 100644 --- a/src/documents/workflows/mutations.py +++ b/src/documents/workflows/mutations.py @@ -105,7 +105,8 @@ def apply_assignment_to_document( field=field, document=document, ).first() - if instance and args[value_field_name] is not None: + # empty string is indistinguishable from no value in the UI + if instance and args[value_field_name] not in (None, ""): setattr(instance, value_field_name, args[value_field_name]) instance.save() elif not instance: