diff --git a/src/documents/bulk_edit.py b/src/documents/bulk_edit.py index 399282dc3..4236397cb 100644 --- a/src/documents/bulk_edit.py +++ b/src/documents/bulk_edit.py @@ -408,10 +408,16 @@ def reprocess(doc_ids: list[int], *, remote_ocr: bool = False) -> Literal["OK"]: Consumption workflows do not run here, so ``remote_ocr`` is how the user asks for the remote engine when it is not configured to handle everything. + + A root document with versions reprocesses its latest version, which is the + file whose content, archive and thumbnail are shown for it. """ - for document_id in doc_ids: + for doc in Document.objects.select_related("root_document").filter( + id__in=doc_ids, + ): + pair = _resolve_root_and_source_doc(doc) update_document_content_maybe_archive_file.apply_async( - kwargs={"document_id": document_id, "remote_ocr": remote_ocr}, + kwargs={"document_id": pair.source_doc.id, "remote_ocr": remote_ocr}, headers={"trigger_source": PaperlessTask.TriggerSource.MANUAL}, ) diff --git a/src/documents/tasks.py b/src/documents/tasks.py index 456b006d9..0a7409f17 100644 --- a/src/documents/tasks.py +++ b/src/documents/tasks.py @@ -490,18 +490,23 @@ def update_document_content_maybe_archive_file( shutil.move(thumbnail, document.thumbnail_path) document.refresh_from_db() + root_document = ( + document.root_document if document.root_document_id else document + ) logger.info( - f"Updating index for document {document_id} ({document.archive_checksum})", + f"Updating index for document {root_document.pk} ({document.archive_checksum})", ) from documents.search import get_backend - get_backend().add_or_update(document) + get_backend().add_or_update(root_document) ai_config = AIConfig() if ai_config.llm_index_enabled: - llm_index_add_or_update_document(document) + llm_index_add_or_update_document(root_document) clear_document_caches(document.pk) + if root_document.pk != document.pk: + clear_document_caches(root_document.pk) except Exception: logger.exception( diff --git a/src/documents/tests/test_bulk_edit.py b/src/documents/tests/test_bulk_edit.py index 1b67a320f..889a66c67 100644 --- a/src/documents/tests/test_bulk_edit.py +++ b/src/documents/tests/test_bulk_edit.py @@ -2021,3 +2021,69 @@ class TestBulkEditReprocess(DirectoriesMixin, TestCase): self.assertEqual(mock_task.apply_async.call_count, 2) for call in mock_task.apply_async.call_args_list: self.assertTrue(call.kwargs["kwargs"]["remote_ocr"]) + + @mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file") + def test_reprocess_root_uses_latest_version(self, mock_task: mock.Mock) -> None: + """ + GIVEN: + - A root document with two versions + WHEN: + - reprocess is called with the root document + THEN: + - The latest version is reprocessed, not the root's original file + """ + Document.objects.create( + title="test", + checksum="A-v1", + mime_type="application/pdf", + root_document=self.doc, + version_index=1, + ) + latest = Document.objects.create( + title="test", + checksum="A-v2", + mime_type="application/pdf", + root_document=self.doc, + version_index=2, + ) + + bulk_edit.reprocess([self.doc.id]) + + mock_task.apply_async.assert_called_once() + self.assertEqual( + mock_task.apply_async.call_args.kwargs["kwargs"]["document_id"], + latest.id, + ) + + @mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file") + def test_reprocess_explicit_version(self, mock_task: mock.Mock) -> None: + """ + GIVEN: + - A root document with two versions + WHEN: + - reprocess is called with the older version + THEN: + - That version is reprocessed + """ + older = Document.objects.create( + title="test", + checksum="A-v1", + mime_type="application/pdf", + root_document=self.doc, + version_index=1, + ) + Document.objects.create( + title="test", + checksum="A-v2", + mime_type="application/pdf", + root_document=self.doc, + version_index=2, + ) + + bulk_edit.reprocess([older.id]) + + mock_task.apply_async.assert_called_once() + self.assertEqual( + mock_task.apply_async.call_args.kwargs["kwargs"]["document_id"], + older.id, + ) diff --git a/src/documents/tests/test_tasks.py b/src/documents/tests/test_tasks.py index 247b6a529..472f998fa 100644 --- a/src/documents/tests/test_tasks.py +++ b/src/documents/tests/test_tasks.py @@ -287,6 +287,61 @@ class TestUpdateContent(DirectoriesMixin, TestCase): tasks.update_document_content_maybe_archive_file(doc.pk) self.assertNotEqual(Document.objects.get(pk=doc.pk).content, "test") + @mock.patch("documents.tasks.clear_document_caches") + @mock.patch("documents.search.get_backend") + def test_update_content_version_indexes_root( + self, + mock_get_backend: mock.Mock, + mock_clear_caches: mock.Mock, + ) -> None: + """ + GIVEN: + - A root document with a version + WHEN: + - Update content task is called for the version + THEN: + - The version's content is updated + - The root document is indexed rather than the version + - Caches are cleared for both + """ + sample1 = self.dirs.scratch_dir / "sample.pdf" + shutil.copy( + Path(__file__).parent + / "samples" + / "documents" + / "originals" + / "0000001.pdf", + sample1, + ) + root = Document.objects.create( + title="test", + content="root content", + checksum="root", + mime_type="application/pdf", + ) + version = Document.objects.create( + title="test", + content="my document", + checksum="wow", + filename=sample1, + mime_type="application/pdf", + root_document=root, + version_index=1, + ) + + tasks.update_document_content_maybe_archive_file(version.pk) + + self.assertNotEqual( + Document.objects.get(pk=version.pk).content, + "my document", + ) + self.assertEqual(Document.objects.get(pk=root.pk).content, "root content") + indexed = mock_get_backend.return_value.add_or_update.call_args.args[0] + self.assertEqual(indexed.pk, root.pk) + mock_clear_caches.assert_has_calls( + [mock.call(version.pk), mock.call(root.pk)], + ) + class TestUpdateContentRemoteOCR(DirectoriesMixin, TestCase): """