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 86e6398ec..35d252b5b 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
@@ -109,13 +109,13 @@
-
-
-
-
+
@for (fieldInstance of document?.custom_fields; track fieldInstance.field; let i = $index) {
@switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
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 b8a6389f2..7dcf4e9f7 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
@@ -80,8 +80,9 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { environment } from 'src/environments/environment'
import { RotateConfirmDialogComponent } from '../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
import { SplitConfirmDialogComponent } from '../common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
-import { PdfViewerModule } from 'ng2-pdf-viewer'
import { DeletePagesConfirmDialogComponent } from '../common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component'
+import { PdfViewerModule } from 'ng2-pdf-viewer'
+import { DataType } from 'src/app/data/datatype'
const doc: Document = {
id: 3,
@@ -783,10 +784,9 @@ describe('DocumentDetailComponent', () => {
const object = {
id: 22,
name: 'Correspondent22',
- last_correspondence: new Date().toISOString(),
} as Correspondent
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
- component.filterDocuments([object])
+ component.filterDocuments([object], DataType.Correspondent)
expect(qfSpy).toHaveBeenCalledWith([
{
rule_type: FILTER_CORRESPONDENT,
@@ -799,7 +799,7 @@ describe('DocumentDetailComponent', () => {
initNormally()
const object = { id: 22, name: 'DocumentType22' } as DocumentType
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
- component.filterDocuments([object])
+ component.filterDocuments([object], DataType.DocumentType)
expect(qfSpy).toHaveBeenCalledWith([
{
rule_type: FILTER_DOCUMENT_TYPE,
@@ -816,7 +816,7 @@ describe('DocumentDetailComponent', () => {
path: '/foo/bar/',
} as StoragePath
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
- component.filterDocuments([object])
+ component.filterDocuments([object], DataType.StoragePath)
expect(qfSpy).toHaveBeenCalledWith([
{
rule_type: FILTER_STORAGE_PATH,
@@ -842,7 +842,7 @@ describe('DocumentDetailComponent', () => {
text_color: '#000000',
} as Tag
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
- component.filterDocuments([object1, object2])
+ component.filterDocuments([object1, object2], DataType.Tag)
expect(qfSpy).toHaveBeenCalledWith([
{
rule_type: FILTER_HAS_TAGS_ALL,
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 23753f55b..a80e401e2 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
@@ -71,6 +71,7 @@ import { RotateConfirmDialogComponent } from '../common/confirm-dialog/rotate-co
import { DeletePagesConfirmDialogComponent } from '../common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component'
import { HotKeyService } from 'src/app/services/hot-key.service'
import { PDFDocumentProxy } from 'ng2-pdf-viewer'
+import { DataType } from 'src/app/data/datatype'
enum DocumentDetailNavIDs {
Details = 1,
@@ -171,6 +172,8 @@ export class DocumentDetailComponent
public readonly ContentRenderType = ContentRenderType
+ public readonly DataType = DataType
+
@ViewChild('nav') nav: NgbNav
@ViewChild('pdfPreview') set pdfPreview(element) {
// this gets called when component added or removed from DOM
@@ -998,7 +1001,7 @@ export class DocumentDetailComponent
)
}
- filterDocuments(items: ObjectWithId[] | NgbDateStruct[]) {
+ filterDocuments(items: ObjectWithId[] | NgbDateStruct[], type?: DataType) {
const filterRules: FilterRule[] = items.flatMap((i) => {
if (i.hasOwnProperty('year')) {
const isoDateAdapter = new ISODateAdapter()
@@ -1017,30 +1020,28 @@ export class DocumentDetailComponent
value: dateBefore.toISOString().substring(0, 10),
},
]
- } else if (i.hasOwnProperty('last_correspondence')) {
- // Correspondent
- return {
- rule_type: FILTER_CORRESPONDENT,
- value: (i as Correspondent).id.toString(),
- }
- } else if (i.hasOwnProperty('path')) {
- // Storage Path
- return {
- rule_type: FILTER_STORAGE_PATH,
- value: (i as StoragePath).id.toString(),
- }
- } else if (i.hasOwnProperty('is_inbox_tag')) {
- // Tag
- return {
- rule_type: FILTER_HAS_TAGS_ALL,
- value: (i as Tag).id.toString(),
- }
- } else {
- // Document Type, has no specific props
- return {
- rule_type: FILTER_DOCUMENT_TYPE,
- value: (i as DocumentType).id.toString(),
- }
+ }
+ switch (type) {
+ case DataType.Correspondent:
+ return {
+ rule_type: FILTER_CORRESPONDENT,
+ value: (i as Correspondent).id.toString(),
+ }
+ case DataType.DocumentType:
+ return {
+ rule_type: FILTER_DOCUMENT_TYPE,
+ value: (i as DocumentType).id.toString(),
+ }
+ case DataType.StoragePath:
+ return {
+ rule_type: FILTER_STORAGE_PATH,
+ value: (i as StoragePath).id.toString(),
+ }
+ case DataType.Tag:
+ return {
+ rule_type: FILTER_HAS_TAGS_ALL,
+ value: (i as Tag).id.toString(),
+ }
}
})
diff --git a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts
index 2d02ba983..c0053353b 100644
--- a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts
+++ b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts
@@ -12,6 +12,7 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
import { ToastService } from 'src/app/services/toast.service'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { ManagementListComponent } from '../management-list/management-list.component'
+import { takeUntil } from 'rxjs'
@Component({
selector: 'pngx-correspondent-list',
@@ -63,6 +64,26 @@ export class CorrespondentListComponent extends ManagementListComponent
{
+ this.data = c.results
+ this.collectionSize = c.count
+ this.isLoading = false
+ })
+ }
+
getDeleteMessage(object: Correspondent) {
return $localize`Do you really want to delete the correspondent "${object.name}"?`
}
diff --git a/src-ui/src/app/components/manage/management-list/management-list.component.ts b/src-ui/src/app/components/manage/management-list/management-list.component.ts
index 3fbf18e09..9453affd5 100644
--- a/src-ui/src/app/components/manage/management-list/management-list.component.ts
+++ b/src-ui/src/app/components/manage/management-list/management-list.component.ts
@@ -52,7 +52,7 @@ export abstract class ManagementListComponent
implements OnInit, OnDestroy
{
constructor(
- private service: AbstractNameFilterService,
+ protected service: AbstractNameFilterService,
private modalService: NgbModal,
private editDialogComponent: any,
private toastService: ToastService,
@@ -81,8 +81,8 @@ export abstract class ManagementListComponent
public isLoading: boolean = false
private nameFilterDebounce: Subject
- private unsubscribeNotifier: Subject = new Subject()
- private _nameFilter: string
+ protected unsubscribeNotifier: Subject = new Subject()
+ protected _nameFilter: string
public selectedObjects: Set = new Set()
public togggleAll: boolean = false
diff --git a/src-ui/src/app/services/rest/abstract-name-filter-service.ts b/src-ui/src/app/services/rest/abstract-name-filter-service.ts
index 1018f0fa2..03c7e5470 100644
--- a/src-ui/src/app/services/rest/abstract-name-filter-service.ts
+++ b/src-ui/src/app/services/rest/abstract-name-filter-service.ts
@@ -17,9 +17,10 @@ export abstract class AbstractNameFilterService<
sortField?: string,
sortReverse?: boolean,
nameFilter?: string,
- fullPerms?: boolean
+ fullPerms?: boolean,
+ extraParams?: { [key: string]: any }
) {
- let params = {}
+ let params = extraParams ?? {}
if (nameFilter) {
params['name__icontains'] = nameFilter
}
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index c92765e69..d7a06e181 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -291,7 +291,7 @@ class OwnedObjectSerializer(
class CorrespondentSerializer(MatchingModelSerializer, OwnedObjectSerializer):
- last_correspondence = serializers.DateTimeField(read_only=True)
+ last_correspondence = serializers.DateTimeField(read_only=True, required=False)
class Meta:
model = Correspondent
diff --git a/src/documents/tests/test_api_objects.py b/src/documents/tests/test_api_objects.py
index 65f379261..1a55a936c 100644
--- a/src/documents/tests/test_api_objects.py
+++ b/src/documents/tests/test_api_objects.py
@@ -1,8 +1,10 @@
+import datetime
import json
from unittest import mock
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
+from django.utils import timezone
from rest_framework import status
from rest_framework.test import APITestCase
@@ -89,6 +91,57 @@ class TestApiObjects(DirectoriesMixin, APITestCase):
results = response.data["results"]
self.assertEqual(len(results), 2)
+ def test_correspondent_last_correspondence(self):
+ """
+ GIVEN:
+ - Correspondent with documents
+ WHEN:
+ - API is called
+ THEN:
+ - Last correspondence date is returned only if requested for list, and for detail
+ """
+
+ Document.objects.create(
+ mime_type="application/pdf",
+ correspondent=self.c1,
+ created=timezone.make_aware(datetime.datetime(2022, 1, 1)),
+ checksum="123",
+ )
+ Document.objects.create(
+ mime_type="application/pdf",
+ correspondent=self.c1,
+ created=timezone.make_aware(datetime.datetime(2022, 1, 2)),
+ checksum="456",
+ )
+
+ # Only if requested for list
+ response = self.client.get(
+ "/api/correspondents/",
+ )
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ results = response.data["results"]
+ self.assertNotIn("last_correspondence", results[0])
+
+ response = self.client.get(
+ "/api/correspondents/?last_correspondence=true",
+ )
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ results = response.data["results"]
+ self.assertIn(
+ "2022-01-02",
+ results[0]["last_correspondence"],
+ )
+
+ # Included in detail by default
+ response = self.client.get(
+ f"/api/correspondents/{self.c1.id}/",
+ )
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertIn(
+ "2022-01-02",
+ response.data["last_correspondence"],
+ )
+
class TestApiStoragePaths(DirectoriesMixin, APITestCase):
ENDPOINT = "/api/storage_paths/"
diff --git a/src/documents/views.py b/src/documents/views.py
index 8b3486f76..91b99b610 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -253,14 +253,7 @@ class PermissionsAwareDocumentCountMixin(PassUserMixin):
class CorrespondentViewSet(ModelViewSet, PermissionsAwareDocumentCountMixin):
model = Correspondent
- queryset = (
- Correspondent.objects.prefetch_related("documents")
- .annotate(
- last_correspondence=Max("documents__created"),
- )
- .select_related("owner")
- .order_by(Lower("name"))
- )
+ queryset = Correspondent.objects.select_related("owner").order_by(Lower("name"))
serializer_class = CorrespondentSerializer
pagination_class = StandardPagination
@@ -279,6 +272,19 @@ class CorrespondentViewSet(ModelViewSet, PermissionsAwareDocumentCountMixin):
"last_correspondence",
)
+ def list(self, request, *args, **kwargs):
+ if request.query_params.get("last_correspondence", None):
+ self.queryset = self.queryset.annotate(
+ last_correspondence=Max("documents__created"),
+ )
+ return super().list(request, *args, **kwargs)
+
+ def retrieve(self, request, *args, **kwargs):
+ self.queryset = self.queryset.annotate(
+ last_correspondence=Max("documents__created"),
+ )
+ return super().retrieve(request, *args, **kwargs)
+
class TagViewSet(ModelViewSet, PermissionsAwareDocumentCountMixin):
model = Tag