diff --git a/CHANGES.rst b/CHANGES.rst
index 1e1cc21..8532d0e 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -6,6 +6,8 @@ Changelog
- Do not let `Recreate session` when it is `sent` or `signed`.
[gbastien]
+- Formalised the sent filename generation in `ISignable.get_filename` (PARAF-550).
+ [chris-adam]
1.0.2 (2026-08-24)
------------------
diff --git a/src/imio/esign/adapters.py b/src/imio/esign/adapters.py
index 5cc9646..08bd080 100644
--- a/src/imio/esign/adapters.py
+++ b/src/imio/esign/adapters.py
@@ -2,6 +2,8 @@
from imio.esign.utils import get_session_annotation
from imio.esign.utils import get_session_info
+from imio.helpers.transmogrifier import get_correct_id
+from os import path
from plone.memoize import ram
from zope.interface import Interface
@@ -80,6 +82,10 @@ def get_discriminators(self):
def get_create_session_custom_data(self):
return {}
+ def get_filename(self, annex, existing_files=()):
+ filename, ext = path.splitext(annex.file.filename or u"no_filename.pdf")
+ return get_correct_id(existing_files, filename) + ext
+
class ISignable(Interface):
def get_signers(self):
@@ -118,3 +124,12 @@ def get_create_session_custom_data(self):
in the annotation.
"""
return {}
+
+ def get_filename(self, annex, existing_files=()):
+ """
+ Return the filename to use for that element in the session.
+
+ `existing_files` holds the names (without extension) already used in the
+ session, to keep them unique.
+ """
+ return u""
diff --git a/src/imio/esign/configure.zcml b/src/imio/esign/configure.zcml
index de4a599..761425e 100644
--- a/src/imio/esign/configure.zcml
+++ b/src/imio/esign/configure.zcml
@@ -18,6 +18,11 @@
+
+
-
-
diff --git a/src/imio/esign/tests/test_adapters.py b/src/imio/esign/tests/test_adapters.py
index b84ac6a..e9328e5 100644
--- a/src/imio/esign/tests/test_adapters.py
+++ b/src/imio/esign/tests/test_adapters.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
from imio.esign.adapters import DefaultContextUidProvider
+from imio.esign.adapters import ISignable
+from imio.esign.adapters import SignableAdapter
from imio.esign.interfaces import IContextUidProvider
from imio.esign.interfaces import IItemOrderProvider
from imio.esign.tests.base import BaseEsignTest
@@ -35,3 +37,25 @@ def test_get_item_order(self):
empty = api.content.create(container=self.portal, type="Folder", id="empty-folder")
provider = getAdapter(empty, IItemOrderProvider)
self.assertEqual(provider.get_item_order(), {})
+
+
+class TestSignableAdapter(BaseEsignTest):
+
+ def test_get_filename(self):
+ annex = self.portal["folder0"]["annex0"]
+ adapter = getAdapter(annex.aq_parent, ISignable)
+ self.assertIsInstance(adapter, SignableAdapter)
+ self.assertEqual(adapter.get_filename(annex), u"annex0.pdf")
+ # a name already used in the session gets a numbered suffix
+ self.assertEqual(adapter.get_filename(annex, existing_files=["annex0"]), u"annex0-1.pdf")
+ self.assertEqual(adapter.get_filename(annex, existing_files=["annex0", "annex0-1"]), u"annex0-2.pdf")
+ # a __ suffix is kept: imio.zamqp parses it back
+ annex.file.filename = u"Rapport__{}.pdf".format(annex.UID())
+ self.assertEqual(adapter.get_filename(annex), u"Rapport__{}.pdf".format(annex.UID()))
+ # and it survives deduplication, the suffix landing after the uid
+ self.assertEqual(
+ adapter.get_filename(annex, existing_files=["Rapport__{}".format(annex.UID())]),
+ u"Rapport__{}-1.pdf".format(annex.UID()),
+ )
+ annex.file.filename = None
+ self.assertEqual(adapter.get_filename(annex), u"no_filename.pdf")
diff --git a/src/imio/esign/tests/test_events.py b/src/imio/esign/tests/test_events.py
new file mode 100644
index 0000000..da1699e
--- /dev/null
+++ b/src/imio/esign/tests/test_events.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+"""events tests for this package."""
+from imio.esign.tests.base import BaseEsignTest
+from imio.esign.utils import add_files_to_session
+from zope.event import notify
+from zope.lifecycleevent import ObjectModifiedEvent
+
+
+class TestEvents(BaseEsignTest):
+ def test_on_categorized_annex_updated(self):
+ """Renaming an annex keeps its session filename unique, without conflicting with itself."""
+ signers = [("user1", "user1@sign.com", "User 1", "Position 1")]
+ annex0 = self.portal["folder0"]["annex0"]
+ annex2 = self.portal["folder0"]["annex2"]
+ sid, session = add_files_to_session(signers, (annex0.UID(), annex2.UID()))[-1]
+ self.assertEqual([f["filename"] for f in session["files"]], [u"annex0.pdf", u"annex2.pdf"])
+ # renamed to a name already used in the session: deduplicated
+ annex2.file.filename = u"annex0.pdf"
+ notify(ObjectModifiedEvent(annex2))
+ self.assertEqual([f["filename"] for f in session["files"]], [u"annex0.pdf", u"annex0-1.pdf"])
+ # updated again: the generated name is the one it already has, it must not drift to annex0-2.pdf
+ annex2.setTitle("New Annex 2")
+ notify(ObjectModifiedEvent(annex2))
+ self.assertEqual([f["filename"] for f in session["files"]], [u"annex0.pdf", u"annex0-1.pdf"])
+ self.assertEqual(session["files"][1]["title"], "New Annex 2")
diff --git a/src/imio/esign/utils.py b/src/imio/esign/utils.py
index ea1baa4..e107f16 100644
--- a/src/imio/esign/utils.py
+++ b/src/imio/esign/utils.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
+from Acquisition import aq_parent
from collections import OrderedDict
from copy import deepcopy
from datetime import datetime
@@ -19,7 +20,6 @@
from imio.esign.interfaces import IContextUidProvider
from imio.esign.interfaces import IItemOrderProvider
from imio.helpers.content import uuidToObject
-from imio.helpers.transmogrifier import get_correct_id
from imio.helpers.ws import get_auth_token
# from imio.pyutils.system import post_request
from imio.pyutils.utils import shortuid_encode_id
@@ -101,6 +101,7 @@ def add_files_to_session( # noqa C901
session = annot["sessions"][session_id]
dispatch = session_id is None
sessions_used = []
+ from imio.esign.adapters import ISignable # avoid circular import
for uid in files_uids:
file_size = get_filesize(uid)
@@ -135,13 +136,11 @@ def add_files_to_session( # noqa C901
logger.info("File with UID %s is already in session_id %s and data were updated!", uid, session_id)
remove_files_from_session([uid], remove_empty_session=False)
- existing_files = [path.splitext(f["filename"])[0] for f in session["files"]]
- filename, ext = path.splitext(annex.file.filename or "no_filename.pdf")
- new_filename = get_correct_id(existing_files, filename)
+ existing_files = [path.splitext(f["filename"])[0] for f in session["files"] if f["uid"] != uid]
file_dict = PersistentMapping(
{
"scan_id": annex.scan_id,
- "filename": new_filename + ext,
+ "filename": ISignable(aq_parent(annex)).get_filename(annex, existing_files=existing_files),
"title": annex.title or "no_title",
"uid": uid,
"context_uid": context_uid,