|
2 | 2 | from collective.contact.plonegroup.browser.tables import OrgaPrettyLinkWithAdditionalInfosColumn as opl_base |
3 | 3 | from collective.dms.basecontent.browser.listing import VersionsTable |
4 | 4 | from collective.dms.basecontent.browser.listing import VersionsTitleColumn |
| 5 | +from collective.eeafaceted.z3ctable.columns import BaseColumn |
5 | 6 | from collective.iconifiedcategory import utils as ic_utils |
6 | 7 | from collective.iconifiedcategory.browser.tabview import CategorizedContent |
7 | 8 | from collective.task import _ as _task |
8 | 9 | from html import escape # noqa F401 |
9 | 10 | from imio.dms.mail import _ |
10 | | -from imio.dms.mail.adapters import OMApprovalAdapter |
| 11 | +from imio.dms.mail.interfaces import IOMApproval |
| 12 | +from imio.esign.config import get_registry_enabled |
| 13 | +from imio.esign.utils import get_session_annotation |
11 | 14 | from imio.helpers.content import uuidToObject |
12 | 15 | from plone import api |
13 | 16 | from Products.CMFPlone.utils import safe_unicode |
14 | 17 | from z3c.table.column import Column |
15 | 18 | from z3c.table.table import Table |
16 | 19 | from zope.cachedescriptors.property import CachedProperty |
| 20 | +from zope.component import getMultiAdapter |
17 | 21 | from zope.component import getUtility |
18 | 22 | from zope.i18n import translate |
19 | 23 | from zope.schema.interfaces import IVocabularyFactory |
@@ -72,6 +76,32 @@ def renderCell(self, content): |
72 | 76 | ) |
73 | 77 |
|
74 | 78 |
|
| 79 | +class SessionIdColumn(BaseColumn): |
| 80 | + """""" |
| 81 | + weight = 10 |
| 82 | + escape = False |
| 83 | + |
| 84 | + def renderHeadCell(self): |
| 85 | + return u'<img src="++resource++imio.esign/parapheo.svg" style="height:1em;vertical-align:middle"> ID' |
| 86 | + |
| 87 | + def renderCell(self, content): |
| 88 | + session_id = self.table._session_annotation.get("uids", {}).get(content.UID, None) |
| 89 | + portal = api.portal.get() |
| 90 | + dashboard_link = getMultiAdapter((portal, portal.REQUEST), name="parapheo").get_dashboard_link({"id": session_id}) |
| 91 | + if session_id is not None and len(self.table._approval.session_ids) > 1: |
| 92 | + return u'<a href={dashboard_link} title="{title}" class="pdf-session-badge">#{session_id}</span>'.format( |
| 93 | + dashboard_link=dashboard_link, |
| 94 | + title=translate( |
| 95 | + u"Paraphéo session ID: ${session_id}", |
| 96 | + domain="imio.dms.mail", |
| 97 | + context=content.REQUEST, |
| 98 | + mapping={'session_id': session_id}, |
| 99 | + ), |
| 100 | + session_id=session_id, |
| 101 | + ) |
| 102 | + return u"" |
| 103 | + |
| 104 | + |
75 | 105 | class EnquirerColumn(Column): |
76 | 106 | """Tasks table viewlet. xss ok""" |
77 | 107 |
|
@@ -127,8 +157,90 @@ class IMVersionsTable(BaseVersionsTable): |
127 | 157 |
|
128 | 158 |
|
129 | 159 | class OMVersionsTable(BaseVersionsTable): |
| 160 | + """Versions table for outgoing mails with PDF child row indentation. |
| 161 | +
|
| 162 | + When the esign approval process generates PDF files from source files, the |
| 163 | + table reorders rows so each PDF child appears directly below its source, and |
| 164 | + adds a ``pdf-child-row`` CSS class for visual indentation (↳ arrow via CSS). |
| 165 | +
|
| 166 | + +----------------------------------+-----+--------+ |
| 167 | + | Title | ... | signed | |
| 168 | + +----------------------------------+-----+--------+ |
| 169 | + | [icon] Source file A | | | |
| 170 | + | ↳ #1 [icon] PDF of A | | ✔ | ← pdf-child-row |
| 171 | + | [icon] File B (no signature) | | | |
| 172 | + | [icon] Source file C | | | |
| 173 | + | ↳ #2 [icon] PDF of C | | ✔ | ← pdf-child-row |
| 174 | + +----------------------------------+-----+--------+ |
| 175 | +
|
| 176 | + Notes: |
| 177 | + - The ``#N`` session badge (OMVersionsTitleColumn) appears only when the |
| 178 | + mail spans multiple esign sessions (len(approval.session_ids) > 1). |
| 179 | + """ |
| 180 | + |
130 | 181 | portal_types = ["dmsommainfile", "dmsappendixfile"] |
131 | 182 |
|
| 183 | + @CachedProperty |
| 184 | + def _approval(self): |
| 185 | + return IOMApproval(self.context) |
| 186 | + |
| 187 | + @CachedProperty |
| 188 | + def _session_annotation(self): |
| 189 | + return get_session_annotation() |
| 190 | + |
| 191 | + @CachedProperty |
| 192 | + def _pdf_child_uids(self): |
| 193 | + """Set of PDF-generated child file UIDs that are distinct from their source.""" |
| 194 | + return set( |
| 195 | + uid for lst in self._approval.pdf_files_uids for uid in lst |
| 196 | + if uid not in self._approval.files_uids |
| 197 | + ) |
| 198 | + |
| 199 | + @property |
| 200 | + def values(self): |
| 201 | + """ |
| 202 | + Values (DMS files and appendixes) in orginal order except children are below their parent. |
| 203 | + Note: Children mean files generated for eSignature, parent is the file originally created by the user. |
| 204 | + """ |
| 205 | + if not getattr(self, '_v_stored_values', []): |
| 206 | + raw_data = BaseVersionsTable.values.fget(self) |
| 207 | + uid_to_content = {c.UID: c for c in raw_data} |
| 208 | + |
| 209 | + # Build parent-to-children UID mapping |
| 210 | + parent_to_children_uids = {} |
| 211 | + for i, source_uid in enumerate(self._approval.files_uids): |
| 212 | + parent_to_children_uids[source_uid] = [ |
| 213 | + uid for uid in self._approval.pdf_files_uids[i] |
| 214 | + if uid in self._pdf_child_uids |
| 215 | + ] |
| 216 | + |
| 217 | + # Build list of values in parent order, but with children below their parent |
| 218 | + result = [] |
| 219 | + for content in raw_data: |
| 220 | + if content.UID in self._pdf_child_uids: |
| 221 | + continue # skip and insert later below parent |
| 222 | + result.append(content) |
| 223 | + for child_uid in parent_to_children_uids.get(content.UID, []): |
| 224 | + if child_uid in uid_to_content: |
| 225 | + result.append(uid_to_content[child_uid]) |
| 226 | + |
| 227 | + self._v_stored_values = result |
| 228 | + return self._v_stored_values |
| 229 | + |
| 230 | + def setUpColumns(self): |
| 231 | + """Removes SessionIdColumn if eSignature is disabled or this mail doesn't belong to any session""" |
| 232 | + columns = super(OMVersionsTable, self).setUpColumns() |
| 233 | + if not get_registry_enabled() or not self.context.UID() in self._session_annotation['c_uids']: |
| 234 | + columns = [col for col in columns if col.__name__ != "session-id-column"] |
| 235 | + return columns |
| 236 | + |
| 237 | + def renderRow(self, row, cssClass=None): |
| 238 | + """Render row with custom css class for children""" |
| 239 | + item = row[0][0] |
| 240 | + if item.UID in self._pdf_child_uids: |
| 241 | + cssClass = (cssClass + ' pdf-child-row') if cssClass else 'pdf-child-row' |
| 242 | + return super(OMVersionsTable, self).renderRow(row, cssClass) |
| 243 | + |
132 | 244 |
|
133 | 245 | class OrgaPrettyLinkWithAdditionalInfosColumn(opl_base): |
134 | 246 | """Plonegroup organizations: removed additional infos. xss ok""" |
@@ -219,7 +331,7 @@ class ApprovalTable(Table): |
219 | 331 |
|
220 | 332 | def __init__(self, context, request): |
221 | 333 | super(ApprovalTable, self).__init__(context, request) |
222 | | - self.approval = OMApprovalAdapter(self.context) |
| 334 | + self.approval = IOMApproval(self.context) |
223 | 335 | self.portal = api.portal.getSite() |
224 | 336 |
|
225 | 337 | def setUpColumns(self): |
|
0 commit comments