Repository: paperless-ngx/paperless-ngx

Issue:
[BUG] 'NoneType' object has no attribute 'archive_serial_number'
### Description

when I want to click +1 as adding document archive numbering, nothing happens - while on the log side, an attribute error pops up. 
In all other documents, document numbering was not applied.

### Steps to reproduce

1. Go to document
2. Click +1 to add archive number

### Webserver logs

```bash
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 534, in thread_handler
    raise exc_info[1]
  File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 42, in inner
    response = await get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 534, in thread_handler
    raise exc_info[1]
  File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
    response = await wrapped_callback(
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 479, in __call__
    ret: _R = await loop.run_in_executor(
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/current_thread_executor.py", line 40, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 538, in thread_handler
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
    return view_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/paperless/src/documents/views.py", line 747, in next_asn
    .archive_serial_number
     ^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'archive_serial_number'
```


### Browser logs

_No response_

### Paperless-ngx version

2.2.3

### Host OS

x86_64

### Installation method

Docker - official image

### Browser

Safari

### Configuration changes

_No response_

### Other

_No response_

### Please confirm the following

- [X] I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
- [X] I have already searched for relevant existing issues and discussions before opening this report.
- [X] I have updated the title field above with a concise description.


Discussion:
I'm assuming you have no documents with an ASN already assigned?
> I'm assuming you have no documents with an ASN already assigned?

Not positive but I thought that is what he meant by 

> In all other documents, document numbering was not applied.

Wouldn’t the `or 0` handle that?

https://github.com/paperless-ngx/paperless-ngx/blob/0068f091bbec05590934b181e4bca02775684950/src/documents/views.py#L745-L749
Yes - while it seems to me that the application should behave in some way in such a situation when the ASN field was never filled in.
for example:

1. The +1 button should not appear until there is any ASN in the database,
2. Tf the button should already appear in the case of the non-existence of an ASN already in the database, when you click +1, a message about the absence of an established ASN numbering scheme should appear in red - so you can not grant the next number in the queue.
> Wouldn’t the or 0 handle that?

No, because the return from the query set is `None`, but the code tries to access the ASN field of it.  Should be simple to fix, I'll handle it.

Resolving patch:
diff --git a/src/documents/views.py b/src/documents/views.py
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -757,16 +757,12 @@ def list(self, request, *args, **kwargs):
 
     @action(detail=False, methods=["GET"], name="Get Next ASN")
     def next_asn(self, request, *args, **kwargs):
-        return Response(
-            (
-                Document.objects.filter(archive_serial_number__gte=0)
-                .order_by("archive_serial_number")
-                .last()
-                .archive_serial_number
-                or 0
-            )
-            + 1,
+        max_asn = Document.objects.aggregate(
+            Max("archive_serial_number", default=0),
+        ).get(
+            "archive_serial_number__max",
         )
+        return Response(max_asn + 1)
 
 
 class LogViewSet(ViewSet):


Test patch:
diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py
--- a/src/documents/tests/test_api_documents.py
+++ b/src/documents/tests/test_api_documents.py
@@ -2051,6 +2051,35 @@ def test_next_asn(self):
         self.assertEqual(resp.status_code, status.HTTP_200_OK)
         self.assertEqual(resp.content, b"1000")
 
+    def test_next_asn_no_documents_with_asn(self):
+        """
+        GIVEN:
+            - Existing document, but with no ASN assugned
+        WHEN:
+            - API request to get next ASN
+        THEN:
+            - ASN 1 is returned
+        """
+        user1 = User.objects.create_user(username="test1")
+        user1.user_permissions.add(*Permission.objects.all())
+        user1.save()
+
+        doc1 = Document.objects.create(
+            title="test",
+            mime_type="application/pdf",
+            content="this is a document 1",
+            checksum="1",
+        )
+        doc1.save()
+
+        self.client.force_authenticate(user1)
+
+        resp = self.client.get(
+            "/api/documents/next_asn/",
+        )
+        self.assertEqual(resp.status_code, status.HTTP_200_OK)
+        self.assertEqual(resp.content, b"1")
+
 
 class TestDocumentApiV2(DirectoriesMixin, APITestCase):
     def setUp(self):