From 9e1036665618ee42e2b10e1dd1e6f38b557d372a Mon Sep 17 00:00:00 2001 From: Chris Adam Date: Thu, 31 Jul 2025 17:21:19 +0200 Subject: [PATCH 01/10] Rebuild POT file --- imio/dms/mail/locales/imio.dms.mail.pot | 265 ++---------------------- 1 file changed, 15 insertions(+), 250 deletions(-) diff --git a/imio/dms/mail/locales/imio.dms.mail.pot b/imio/dms/mail/locales/imio.dms.mail.pot index e0bbe8af..454c12bb 100644 --- a/imio/dms/mail/locales/imio.dms.mail.pot +++ b/imio/dms/mail/locales/imio.dms.mail.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2025-05-18 07:19+0000\n" +"POT-Creation-Date: 2025-07-31 15:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,10 +17,6 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: imio.dms.mail\n" -#: ./vocabularies.py -msgid "${element_title} (Inactive)" -msgstr "" - #: ./browser/settings.py:655 msgid "${tab} tab: multiple value '${value}' in '${field}' setting !! Must be unique" msgstr "" @@ -45,7 +41,7 @@ msgstr "" msgid "${tab} tab: « ${field} » rule ${rule} is configured with no values defined" msgstr "" -#: ./browser/views.py:44 +#: ./browser/views.py:45 msgid "${title}: create from template" msgstr "" @@ -61,26 +57,10 @@ msgstr "" msgid "${type} state set" msgstr "" -#: ./browser/settings.py -msgid "'${field}': position ${position}" -msgstr "" - -#: ./__init__.py:26 -msgid "++resource++imio.dms.mail/wf_again.png" -msgstr "" - -#: ./__init__.py:25 -msgid "++resource++imio.dms.mail/wf_back.png" -msgstr "" - #: ./subscribers.py:731 msgid "Linked groups : ${list}" msgstr "" -#: ./browser/settings.py:422 -msgid "Unconfigured fields are: ${list}" -msgstr "" - #: ./browser/settings.py:583 msgid "A user can see all mail titles linked to a contact." msgstr "" @@ -121,18 +101,10 @@ msgstr "" msgid "All contacts export" msgstr "" -#: ./browser/views.py:89 -msgid "All under" -msgstr "" - #: ./browser/batchactions.py:69 msgid "An assigned user is not in this new treating group. Mail \"${mail}\" !" msgstr "" -#: ./browser/batchactions.py:331 -msgid "Apply" -msgstr "" - #: ./browser/templates/actions_panel_assign_user.pt:11 msgid "Assign" msgstr "" @@ -157,10 +129,6 @@ msgstr "" msgid "Base template" msgstr "" -#: ./browser/batchactions.py:51 -msgid "Batch action form" -msgstr "" - #: ./browser/batchactions.py:218 msgid "Batch copy to" msgstr "" @@ -193,15 +161,11 @@ msgstr "" msgid "Batch sender contact field change" msgstr "" -#: ./browser/batchactions.py:134 -msgid "Batch state change" -msgstr "" - #: ./browser/batchactions.py:41 msgid "Batch treating group change" msgstr "" -#: ./browser/views.py:242 +#: ./browser/views.py:243 msgid "Cannot use localhost as smtp" msgstr "" @@ -226,22 +190,14 @@ msgstr "" msgid "Choose a value !" msgstr "" -#: ./browser/views.py:49 +#: ./browser/views.py:50 msgid "Choose this template" msgstr "" -#: ./browser/settings.py:373 -msgid "Choose to which state a manually forwarded email will go." -msgstr "" - #: ./browser/settings.py:556 msgid "Close outgoing mail on email send" msgstr "" -#: ./browser/batchactions.py:148 -msgid "Comment" -msgstr "" - #: ./setuphandlers.py:2453 msgid "Common templates" msgstr "" @@ -272,10 +228,6 @@ msgstr "" msgid "Contacts audit" msgstr "" -#: ./setuphandlers.py:1836 -msgid "Contacts export" -msgstr "" - #: ./browser/templates/actions_panel_create_from_template.pt:6 msgid "Create from template" msgstr "" @@ -288,7 +240,7 @@ msgstr "" msgid "Creating groups : to be used in kofax index" msgstr "" -#: ./browser/settings.py:1161 +#: ./browser/settings.py:1162 msgid "Current product version" msgstr "" @@ -324,11 +276,11 @@ msgstr "" msgid "Do mailing for each postal sending type." msgstr "" -#: ./browser/settings.py:1149 +#: ./browser/settings.py:1150 msgid "Document viewer preservation date" msgstr "" -#: ./browser/settings.py:1145 +#: ./browser/settings.py:1146 msgid "Document viewer preservation days number" msgstr "" @@ -340,22 +292,10 @@ msgstr "" msgid "Email general template" msgstr "" -#: ./browser/settings.py:372 -msgid "Email manual forward transition" -msgstr "" - -#: ./browser/views.py:216 -msgid "Email sent at ${date_hour}." -msgstr "" - #: ./browser/settings.py:575 msgid "Email signature model" msgstr "" -#: ./vocabularies.py:135 -msgid "Empty value" -msgstr "" - #: ./browser/settings.py:551 msgid "Enable edition of service email templates for encoder" msgstr "" @@ -437,10 +377,6 @@ msgstr "" msgid "Groups hidden in dashboards filter" msgstr "" -#: ./adapters.py: -msgid "Has response icon" -msgstr "" - #: ./setuphandlers.py:2600 msgid "Header template" msgstr "" @@ -489,7 +425,7 @@ msgstr "" msgid "Incoming mails" msgstr "" -#: ./browser/settings.py:1153 +#: ./browser/settings.py:1154 msgid "Incoming mails folder period (month, week, day)" msgstr "" @@ -530,10 +466,6 @@ msgstr "" msgid "Mail type" msgstr "" -#: ./browser/table.py:75 -msgid "Mailing" -msgstr "" - #: ./setuphandlers.py:2624 msgid "Mailing template" msgstr "" @@ -542,10 +474,6 @@ msgstr "" msgid "Member Area" msgstr "" -#: ./browser/settings.py:460 -msgid "Missing mandatory fields: ${msg}" -msgstr "" - #: ./browser/templates/actions_panel_folder_annexes.pt:5 msgid "Multiple annexes" msgstr "" @@ -554,14 +482,6 @@ msgstr "" msgid "No check" msgstr "" -#: ./browser/batchactions.py:144 -msgid "No common or available transition. Modify your selection." -msgstr "" - -#: ./browser/batchactions.py:193 -msgid "No common or available treating group, or no available assigned user. Modify your selection." -msgstr "" - #: ./browser/batchactions.py:241 msgid "No folder available where you can add templates." msgstr "" @@ -582,10 +502,6 @@ msgstr "" msgid "Once created and used, value doesn't be changed anymore. None can be used for a 'choose' value." msgstr "" -#: ./browser/batchactions.py:149 -msgid "Optional comment to display in history" -msgstr "" - #: ./migrations/migrate_to_2_1.py:390 #: ./setuphandlers.py:355 msgid "Organizations" @@ -638,7 +554,7 @@ msgstr "" msgid "Outgoing mails" msgstr "" -#: ./browser/settings.py:1157 +#: ./browser/settings.py:1158 msgid "Outgoing mails folder period (month, week, day)" msgstr "" @@ -652,18 +568,10 @@ msgstr "" msgid "Persons searches" msgstr "" -#: ./profiles/default/registry.xml -msgid "Pipeline to use to import contacts" -msgstr "" - #: ./wfadaptations.py:385 msgid "Please update manually ${type} local roles for creating_group !" msgstr "" -#: ./browser/settings.py:466 -msgid "Position required for fields: ${msg}" -msgstr "" - #: ./browser/settings.py:503 msgid "Post mailing" msgstr "" @@ -696,10 +604,6 @@ msgstr "" msgid "Relative path" msgstr "" -#: ./adapters.py: -msgid "Remark icon" -msgstr "" - #: ./browser/templates/actions_panel_reply.pt:5 msgid "Reply" msgstr "" @@ -798,10 +702,6 @@ msgstr "" msgid "TAL condition 2" msgstr "" -#: ./browser/settings.py:257 -msgid "TAL condition 3" -msgstr "" - #: ./migrations/migrate_to_1_0.py:80 #: ./migrations/migrate_to_2_0.py:110 #: ./setuphandlers.py:298 @@ -822,10 +722,6 @@ msgstr "" msgid "The assigned user is not in the selected group !" msgstr "" -#: ./browser/viewlets.py:115 -msgid "This contact '${title}' has missing address fields: ${keys}" -msgstr "" - #: ./wfadaptations.py:53 msgid "This workflow adaptation is only valid for ${workflow} !" msgstr "" @@ -838,10 +734,6 @@ msgstr "" msgid "Transferer" msgstr "" -#: ./browser/batchactions.py:141 -msgid "Transition" -msgstr "" - #: ./browser/settings.py:254 msgid "Treating group value" msgstr "" @@ -936,11 +828,11 @@ msgstr "" msgid "You must select an assigned user before you can propose to an agent !" msgstr "" -#: ./browser/views.py:270 +#: ./browser/views.py:271 msgid "Your email has been sent." msgstr "" -#: ./browser/views.py:242 +#: ./browser/views.py:243 msgid "Your email has not been sent: ${error}." msgstr "" @@ -982,18 +874,6 @@ msgstr "" msgid "backrefs_viewlet_title" msgstr "" -#: ./columns.py:56 -msgid "batch_printable_False" -msgstr "" - -#: ./columns.py:56 -msgid "batch_printable_True" -msgstr "" - -#: ./browser/viewlets.py:107 -msgid "city" -msgstr "" - #: ./setuphandlers.py:127 msgid "classification_tree_tab" msgstr "" @@ -1006,14 +886,6 @@ msgstr "" msgid "create_classification_folder" msgstr "" -#: ./browser/templates/category_contact.pt:11 -msgid "create_contact" -msgstr "" - -#: ./browser/templates/category_contact.pt:11 -msgid "create_contact_list" -msgstr "" - #: ./browser/templates/category_im.pt:17 msgid "create_iem" msgstr "" @@ -1022,22 +894,10 @@ msgstr "" msgid "create_im" msgstr "" -#: ./browser/templates/category_om.pt:17 -msgid "create_oem" -msgstr "" - #: ./browser/templates/category_om.pt:10 msgid "create_om" msgstr "" -#: ./browser/templates/category_contact.pt:11 -msgid "create_organization" -msgstr "" - -#: ./browser/templates/category_contact.pt:11 -msgid "create_person" -msgstr "" - #: ./setuphandlers.py:116 msgid "folders_tab" msgstr "" @@ -1046,10 +906,6 @@ msgstr "" msgid "followed" msgstr "" -#: ./browser/settings.py -msgid "for '${conf}' config => ${fields}. " -msgstr "" - #: ./migrations/migrate_to_2_0.py:251 #: ./migrations/migrate_to_2_1.py:292 #: ./migrations/migrate_to_2_3.py:170 @@ -1106,10 +962,6 @@ msgstr "" msgid "im_to_validate" msgstr "" -#: ./browser/settings.py -msgid "imail_fields" -msgstr "" - #: ./profiles.zcml:14 msgid "imio.dms.mail" msgstr "" @@ -1138,10 +990,6 @@ msgstr "" msgid "in_my_group" msgstr "" -#: ./browser/reply_form.py -msgid "incoming mails" -msgstr "" - #: ./setuphandlers.py:231 msgid "incoming_mail_tab" msgstr "" @@ -1151,10 +999,6 @@ msgstr "" msgid "listing_date_title" msgstr "" -#: ./browser/listing.py:31 -msgid "listing_no_group" -msgstr "" - #. Default: "Object" #: ./browser/templates/listing.pt:31 msgid "listing_object_title" @@ -1165,10 +1009,6 @@ msgstr "" msgid "listing_sender_title" msgstr "" -#: ./browser/viewlets.py:107 -msgid "number" -msgstr "" - #: ./setuphandlers.py:1505 msgid "om_have_treated" msgstr "" @@ -1197,18 +1037,14 @@ msgstr "" msgid "om_to_validate" msgstr "" -#: ./browser/settings.py -msgid "omail_fields" -msgstr "" - -#: ./profiles/default/registry.xml -msgid "organization_type_query_field" -msgstr "" - #: ./setuphandlers.py:262 msgid "outgoing_mail_tab" msgstr "" +#: ./browser/tabular_view.py:62 +msgid "plone" +msgstr "" + #. Default: "You have been redirected here because you do not have access anymore to the element you just edited." #: ./subscribers.py:349 msgid "redirected_after_edition" @@ -1218,73 +1054,6 @@ msgstr "" msgid "searchfor: ${state}" msgstr "" -#: contacts -msgid "searchfor_active" -msgstr "" - -#: dmsincomingmail -#: task -msgid "searchfor_closed" -msgstr "" - -#: dmsincomingmail -#: dmsoutgoingmail -#: task -msgid "searchfor_created" -msgstr "" - -#: contacts -msgid "searchfor_deactivated" -msgstr "" - -#: task -msgid "searchfor_in_progress" -msgstr "" - -#: dmsincomingmail -msgid "searchfor_in_treatment" -msgstr "" - -#: dmsincomingmail -msgid "searchfor_proposed_to_agent" -msgstr "" - -#: dmsincomingmail -msgid "searchfor_proposed_to_manager" -msgstr "" - -#: task -msgid "searchfor_realized" -msgstr "" - -#: dmsoutgoingmail -msgid "searchfor_scanned" -msgstr "" - -#: dmsoutgoingmail -msgid "searchfor_sent" -msgstr "" - -#: task -msgid "searchfor_to_assign" -msgstr "" - -#: dmsoutgoingmail -msgid "searchfor_to_be_signed" -msgstr "" - -#: task -msgid "searchfor_to_do" -msgstr "" - -#: dmsoutgoingmail -msgid "searchfor_to_print" -msgstr "" - -#: ./browser/viewlets.py:107 -msgid "street" -msgstr "" - #: ./setuphandlers.py:1177 msgid "task_have_treated" msgstr "" @@ -1328,7 +1097,3 @@ msgstr "" #: ./setuphandlers.py:2451 msgid "templates_tab" msgstr "" - -#: ./browser/viewlets.py:107 -msgid "zip_code" -msgstr "" From 13c936dd6f8c5684ef48521c2b0946c024f681f6 Mon Sep 17 00:00:00 2001 From: Chris Adam Date: Wed, 30 Jul 2025 14:12:13 +0200 Subject: [PATCH 02/10] Add duplicate action for outgoing mails WIP --- imio/dms/mail/browser/actionspanel.py | 13 ++ imio/dms/mail/browser/configure.zcml | 7 + imio/dms/mail/browser/settings.py | 33 ++++ imio/dms/mail/browser/static/copy.svg | 3 + .../templates/actions_panel_duplicate.pt | 10 + imio/dms/mail/browser/views.py | 124 ++++++++++++ .../locales/en/LC_MESSAGES/imio.dms.mail.po | 57 ++++++ .../locales/fr/LC_MESSAGES/imio.dms.mail.po | 57 ++++++ imio/dms/mail/locales/imio.dms.mail.pot | 141 ++++++++++---- .../skins/imio_dms_mail/imiodmsmail.css.dtml | 4 + imio/dms/mail/tests/test_views.py | 182 ++++++++++++++++++ 11 files changed, 589 insertions(+), 42 deletions(-) create mode 100644 imio/dms/mail/browser/static/copy.svg create mode 100644 imio/dms/mail/browser/templates/actions_panel_duplicate.pt diff --git a/imio/dms/mail/browser/actionspanel.py b/imio/dms/mail/browser/actionspanel.py index a8bfe21c..3099892f 100644 --- a/imio/dms/mail/browser/actionspanel.py +++ b/imio/dms/mail/browser/actionspanel.py @@ -184,6 +184,7 @@ def __init__(self, context, request): self.ACCEPTABLE_ACTIONS = ["delete"] self.SECTIONS_TO_RENDER += ( "render_create_from_template_button", + "render_duplicate_button", "render_create_new_message", "render_send_email", ) @@ -207,6 +208,18 @@ def render_create_from_template_button(self): return ViewPageTemplateFile("templates/actions_panel_create_from_template.pt")(self) return "" + def may_duplicate(self): + """ + Method that check if special 'duplicate' action has to be displayed. + """ + # TODO define permissions for duplicate + return True + + def render_duplicate_button(self): + if self.may_duplicate(): + return ViewPageTemplateFile("templates/actions_panel_duplicate.pt")(self) + return "" + def may_create_new_message(self): if ( self.context.is_email() diff --git a/imio/dms/mail/browser/configure.zcml b/imio/dms/mail/browser/configure.zcml index 80666ecf..dad1eefd 100644 --- a/imio/dms/mail/browser/configure.zcml +++ b/imio/dms/mail/browser/configure.zcml @@ -18,6 +18,13 @@ class=".views.CreateFromTemplateForm" /> + + + + \ No newline at end of file diff --git a/imio/dms/mail/browser/templates/actions_panel_duplicate.pt b/imio/dms/mail/browser/templates/actions_panel_duplicate.pt new file mode 100644 index 00000000..144b0574 --- /dev/null +++ b/imio/dms/mail/browser/templates/actions_panel_duplicate.pt @@ -0,0 +1,10 @@ + + + + + + + diff --git a/imio/dms/mail/browser/views.py b/imio/dms/mail/browser/views.py index e95e0ee3..a1fcd618 100644 --- a/imio/dms/mail/browser/views.py +++ b/imio/dms/mail/browser/views.py @@ -1,11 +1,15 @@ # -*- coding: utf-8 -*- from AccessControl import getSecurityManager from collective.ckeditortemplates.cktemplate import ICKTemplate +from collective.dms.basecontent.dmsfile import IDmsAppendixFile +from collective.dms.mailcontent.dmsmail import internalReferenceOutgoingMailDefaultValue from datetime import datetime +from DateTime import DateTime from eea.faceted.vocabularies.autocomplete import IAutocompleteSuggest from imio.dms.mail import _ from imio.dms.mail import _tr from imio.dms.mail import PMH_ENABLED +from imio.dms.mail.browser.settings import IImioDmsMailConfig from imio.dms.mail.browser.table import CKTemplatesTable from imio.dms.mail.browser.table import PersonnelTable from imio.dms.mail.dmsfile import IImioDmsFile @@ -20,14 +24,23 @@ from imio.helpers.workflow import do_transitions from imio.helpers.xhtml import object_link from plone import api +from plone.supermodel import model +from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import safe_unicode from Products.Five import BrowserView from Products.PageTemplates.Expressions import SecureModuleImporter from unidecode import unidecode # unidecode_expect_nonascii not yet available in used version +from z3c.form import button +from z3c.form.field import Fields +from z3c.form.form import Form +from z3c.relationfield import RelationValue +from zope import schema from zope.annotation import IAnnotations from zope.component import getMultiAdapter +from zope.component import getUtility from zope.i18n import translate from zope.interface import implements +from zope.intid.interfaces import IIntIds from zope.lifecycleevent import modified from zope.pagetemplate.pagetemplate import PageTemplate @@ -70,6 +83,117 @@ def redirect_url(self, uid): return "{}/persistent-document-generation?{}".format(url, "&".join(params)) +class IDuplicateFormSchema(model.Schema): + + keep_category = schema.Bool( + title=_(u"Keep classification category"), + description=u'', + default=True, + ) + + keep_folder = schema.Bool( + title=_(u"Keep classification folder"), + description=u'', + default=True, + ) + + keep_linked_mails = schema.Bool( + title=_(u"Keep linked mails"), + description=u'', + default=True, + ) + + keep_dms_files = schema.Bool( + title=_(u"Keep DMS files"), + description=u'', + default=True, + ) + + keep_annexes = schema.Bool( + title=_(u"Keep annexes"), + description=u'', + default=True, + ) + + link_to_original = schema.Bool( + title=_(u"Link to original"), + description=u'', + default=True, + ) + + +class DuplicateForm(Form): + + """Duplicate an outgoing mail.""" + label = _(u"Duplicate mail") + fields = Fields(IDuplicateFormSchema) + ignoreContext = True + + @button.buttonAndHandler(_('Duplicate'), name='duplicate') + def handleApply(self, action): + data, errors = self.extractData() + + if errors: + self.status = self.formErrorsMessage + return + + # Duplicate the mail + parent = self.context.aq_parent + clipboard = parent.manage_copyObjects([self.context.getId()]) + result = parent.manage_pasteObjects(clipboard) + duplicated_mail = parent[result[0]['new_id']] + duplicated_mail.creation_date = DateTime() + duplicated_mail.reindexObject(idxs=['created']) + duplicated_mail.internal_reference_no = internalReferenceOutgoingMailDefaultValue(self) + duplicated_mail.due_date = None + duplicated_mail.outgoing_date = None + duplicated_mail.mail_date = None + + if not data['keep_category']: + duplicated_mail.classification_categories = None + + if not data['keep_folder']: + duplicated_mail.classification_folders = None + + if not data['keep_linked_mails']: + duplicated_mail.reply_to = None + + if not data['keep_dms_files']: + dms_files = [sub_content.getId() for sub_content in duplicated_mail.values() if IImioDmsFile.providedBy(sub_content)] + if dms_files: + duplicated_mail.manage_delObjects(dms_files) + + if not data['keep_annexes']: + annexes = [sub_content.getId() for sub_content in duplicated_mail.values() if IDmsAppendixFile.providedBy(sub_content)] + if annexes: + duplicated_mail.manage_delObjects(annexes) + + if data['link_to_original']: + intids = getUtility(IIntIds) + rel_id = intids.getId(self.context) + if duplicated_mail.reply_to is None: + duplicated_mail.reply_to = [] + duplicated_mail.reply_to.append(RelationValue(rel_id)) + + self.request.response.redirect(duplicated_mail.absolute_url()+"/edit") + + def updateWidgets(self): + super(DuplicateForm, self).updateWidgets() + self.widgets["keep_category"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_category", IImioDmsMailConfig, True) else [] + self.widgets["keep_folder"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_folder", IImioDmsMailConfig, True) else [] + self.widgets["keep_linked_mails"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_linked_mails", IImioDmsMailConfig, True) else [] + self.widgets["keep_dms_files"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_dms_files", IImioDmsMailConfig, True) else [] + self.widgets["keep_annexes"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_annexes", IImioDmsMailConfig, True) else [] + self.widgets["link_to_original"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_link_to_original", IImioDmsMailConfig, True) else [] + + navtree_props = getToolByName(api.portal.get(), 'portal_properties').navtree_properties + excluded_types = navtree_props.getProperty('metaTypesNotToList', ()) + if 'ClassificationContainer' in excluded_types: + self.widgets["keep_category"].mode = "hidden" + if 'ClassificationFolders' in excluded_types: + self.widgets["keep_folder"].mode = "hidden" + + def parse_query(text): """Copied from plone.app.vocabularies.catalog.parse_query but cleaned.""" for char in "?-+*()": diff --git a/imio/dms/mail/locales/en/LC_MESSAGES/imio.dms.mail.po b/imio/dms/mail/locales/en/LC_MESSAGES/imio.dms.mail.po index 6a596752..b695fac6 100644 --- a/imio/dms/mail/locales/en/LC_MESSAGES/imio.dms.mail.po +++ b/imio/dms/mail/locales/en/LC_MESSAGES/imio.dms.mail.po @@ -1330,3 +1330,60 @@ msgstr "Templates" #: ./browser/viewlets.py:107 msgid "zip_code" msgstr "" + +#: ./browser/settings.py:556 +msgid "Default value when duplicating an outgoing mail for 'Keep DMS files'" +msgstr "" + +#: ./browser/settings.py:560 +msgid "Default value when duplicating an outgoing mail for 'Keep annexes'" +msgstr "" + +#: ./browser/settings.py:544 +msgid "Default value when duplicating an outgoing mail for 'Keep classification category'" +msgstr "" + +#: ./browser/settings.py:548 +msgid "Default value when duplicating an outgoing mail for 'Keep classification folder'" +msgstr "" + +#: ./browser/settings.py:552 +msgid "Default value when duplicating an outgoing mail for 'Keep linked mails'" +msgstr "" + +#: ./browser/settings.py:564 +msgid "Default value when duplicating an outgoing mail for 'Link to original'" +msgstr "" + +#: ./browser/views.py:108 +msgid "Keep DMS files" +msgstr "" + +#: ./browser/views.py:115 +msgid "Keep annexes" +msgstr "" + +#: ./browser/views.py:87 +msgid "Keep classification category" +msgstr "" + +#: ./browser/views.py:94 +msgid "Keep classification folder" +msgstr "" + +#: ./browser/views.py:101 +msgid "Keep linked mails" +msgstr "" + +#: ./browser/views.py:122 +msgid "Link to original" +msgstr "" + +#: ./browser/templates/actions_panel_duplicate.pt:6 +#: ./browser/views.py:136 +msgid "Duplicate" +msgstr "" + +#: ./browser/views.py:132 +msgid "Duplicate mail" +msgstr "" diff --git a/imio/dms/mail/locales/fr/LC_MESSAGES/imio.dms.mail.po b/imio/dms/mail/locales/fr/LC_MESSAGES/imio.dms.mail.po index 538b2fd8..e8f16824 100644 --- a/imio/dms/mail/locales/fr/LC_MESSAGES/imio.dms.mail.po +++ b/imio/dms/mail/locales/fr/LC_MESSAGES/imio.dms.mail.po @@ -1332,3 +1332,60 @@ msgstr "Modèles" #: ./browser/viewlets.py:107 msgid "zip_code" msgstr "Code postal" + +#: ./browser/settings.py:556 +msgid "Default value when duplicating an outgoing mail for 'Keep DMS files'" +msgstr "Valeur par défaut pour le champ 'Garder les fichiers GED' lorsqu'on duplique un courrier sortant" + +#: ./browser/settings.py:560 +msgid "Default value when duplicating an outgoing mail for 'Keep annexes'" +msgstr "Valeur par défaut pour le champ 'Garder les annexes' lorsqu'on duplique un courrier sortant" + +#: ./browser/settings.py:544 +msgid "Default value when duplicating an outgoing mail for 'Keep classification category'" +msgstr "Valeur par défaut pour le champ 'Garder les catégories' lorsqu'on duplique un courrier sortant" + +#: ./browser/settings.py:548 +msgid "Default value when duplicating an outgoing mail for 'Keep classification folder'" +msgstr "Valeur par défaut pour le champ 'Garder les dossiers' lorsqu'on duplique un courrier sortant" + +#: ./browser/settings.py:552 +msgid "Default value when duplicating an outgoing mail for 'Keep linked mails'" +msgstr "Valeur par défaut pour le champ 'Garder les courriers liés' lorsqu'on duplique un courrier sortant" + +#: ./browser/settings.py:564 +msgid "Default value when duplicating an outgoing mail for 'Link to original'" +msgstr "Valeur par défaut pour le champ 'Lier le courrier dupliqué à l'original' lorsqu'on duplique un courrier sortant" + +#: ./browser/views.py:108 +msgid "Keep DMS files" +msgstr "Garder les fichiers GED" + +#: ./browser/views.py:115 +msgid "Keep annexes" +msgstr "Garder les annexes" + +#: ./browser/views.py:87 +msgid "Keep classification category" +msgstr "Garder les catégories" + +#: ./browser/views.py:94 +msgid "Keep classification folder" +msgstr "Garder les dossiers" + +#: ./browser/views.py:101 +msgid "Keep linked mails" +msgstr "Garder les courriers liés" + +#: ./browser/views.py:122 +msgid "Link to original" +msgstr "Lier le courrier dupliqué à l'original" + +#: ./browser/templates/actions_panel_duplicate.pt:6 +#: ./browser/views.py:136 +msgid "Duplicate" +msgstr "Dupliquer" + +#: ./browser/views.py:132 +msgid "Duplicate mail" +msgstr "Dupliquer le courrier" diff --git a/imio/dms/mail/locales/imio.dms.mail.pot b/imio/dms/mail/locales/imio.dms.mail.pot index 454c12bb..f318b6b7 100644 --- a/imio/dms/mail/locales/imio.dms.mail.pot +++ b/imio/dms/mail/locales/imio.dms.mail.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2025-07-31 15:20+0000\n" +"POT-Creation-Date: 2025-07-31 15:22+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,31 +17,31 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: imio.dms.mail\n" -#: ./browser/settings.py:655 +#: ./browser/settings.py:687 msgid "${tab} tab: multiple value '${value}' in '${field}' setting !! Must be unique" msgstr "" -#: ./browser/settings.py:737 +#: ./browser/settings.py:769 msgid "${tab} tab: send_modes field must have values starting with « post », « email » or « other »" msgstr "" -#: ./browser/settings.py:673 +#: ./browser/settings.py:705 msgid "${tab} tab: unchecking '${field}' setting is not expected !!" msgstr "" -#: ./browser/settings.py:696 +#: ./browser/settings.py:728 msgid "${tab} tab: « ${field} » rule ${rule} has an invalid pattern in « ${col} »" msgstr "" -#: ./browser/settings.py:717 +#: ./browser/settings.py:749 msgid "${tab} tab: « ${field} » rule ${rule} is configured with an assigned user « ${user} » not in the corresponding treating group « ${tg} »" msgstr "" -#: ./browser/settings.py:704 +#: ./browser/settings.py:736 msgid "${tab} tab: « ${field} » rule ${rule} is configured with no values defined" msgstr "" -#: ./browser/views.py:45 +#: ./browser/views.py:56 msgid "${title}: create from template" msgstr "" @@ -61,7 +61,7 @@ msgstr "" msgid "Linked groups : ${list}" msgstr "" -#: ./browser/settings.py:583 +#: ./browser/settings.py:615 msgid "A user can see all mail titles linked to a contact." msgstr "" @@ -165,15 +165,15 @@ msgstr "" msgid "Batch treating group change" msgstr "" -#: ./browser/views.py:243 +#: ./browser/views.py:354 msgid "Cannot use localhost as smtp" msgstr "" -#: ./browser/settings.py:552 +#: ./browser/settings.py:584 msgid "Check if a service encoder can edit his service email templates." msgstr "" -#: ./browser/settings.py:482 +#: ./browser/settings.py:488 msgid "Check if a service encoder can edit his service office templates." msgstr "" @@ -190,11 +190,11 @@ msgstr "" msgid "Choose a value !" msgstr "" -#: ./browser/views.py:50 +#: ./browser/views.py:61 msgid "Choose this template" msgstr "" -#: ./browser/settings.py:556 +#: ./browser/settings.py:588 msgid "Close outgoing mail on email send" msgstr "" @@ -220,7 +220,7 @@ msgstr "" msgid "Contact lists" msgstr "" -#: ./browser/settings.py:581 +#: ./browser/settings.py:613 msgid "Contacts" msgstr "" @@ -240,7 +240,7 @@ msgstr "" msgid "Creating groups : to be used in kofax index" msgstr "" -#: ./browser/settings.py:1162 +#: ./browser/settings.py:1194 msgid "Current product version" msgstr "" @@ -252,10 +252,34 @@ msgstr "" msgid "Data transfer" msgstr "" -#: ./browser/settings.py:567 +#: ./browser/settings.py:599 msgid "Default bcc emails" msgstr "" +#: ./browser/settings.py:556 +msgid "Default value when duplicating an outgoing mail for 'Keep DMS files'" +msgstr "" + +#: ./browser/settings.py:560 +msgid "Default value when duplicating an outgoing mail for 'Keep annexes'" +msgstr "" + +#: ./browser/settings.py:544 +msgid "Default value when duplicating an outgoing mail for 'Keep classification category'" +msgstr "" + +#: ./browser/settings.py:548 +msgid "Default value when duplicating an outgoing mail for 'Keep classification folder'" +msgstr "" + +#: ./browser/settings.py:552 +msgid "Default value when duplicating an outgoing mail for 'Keep linked mails'" +msgstr "" + +#: ./browser/settings.py:564 +msgid "Default value when duplicating an outgoing mail for 'Link to original'" +msgstr "" + #: ./browser/settings.py:315 msgid "Displayed title" msgstr "" @@ -268,19 +292,19 @@ msgstr "" msgid "Dms Mail Data Transfer" msgstr "" -#: ./browser/settings.py:476 +#: ./browser/settings.py:482 msgid "Dms file must be an odt format" msgstr "" -#: ./browser/settings.py:504 +#: ./browser/settings.py:510 msgid "Do mailing for each postal sending type." msgstr "" -#: ./browser/settings.py:1150 +#: ./browser/settings.py:1182 msgid "Document viewer preservation date" msgstr "" -#: ./browser/settings.py:1146 +#: ./browser/settings.py:1178 msgid "Document viewer preservation days number" msgstr "" @@ -288,19 +312,28 @@ msgstr "" msgid "Due date extension" msgstr "" +#: ./browser/templates/actions_panel_duplicate.pt:6 +#: ./browser/views.py:136 +msgid "Duplicate" +msgstr "" + +#: ./browser/views.py:132 +msgid "Duplicate mail" +msgstr "" + #: ./setuphandlers.py:2746 msgid "Email general template" msgstr "" -#: ./browser/settings.py:575 +#: ./browser/settings.py:607 msgid "Email signature model" msgstr "" -#: ./browser/settings.py:551 +#: ./browser/settings.py:583 msgid "Enable edition of service email templates for encoder" msgstr "" -#: ./browser/settings.py:481 +#: ./browser/settings.py:487 msgid "Enable edition of service office templates for encoder" msgstr "" @@ -361,7 +394,7 @@ msgstr "" msgid "From primary organization" msgstr "" -#: ./browser/settings.py:561 +#: ./browser/settings.py:593 msgid "From where to get sender default email" msgstr "" @@ -369,11 +402,11 @@ msgstr "" msgid "Full documentation" msgstr "" -#: ./browser/settings.py:600 +#: ./browser/settings.py:632 msgid "General config tab" msgstr "" -#: ./browser/settings.py:605 +#: ./browser/settings.py:637 msgid "Groups hidden in dashboards filter" msgstr "" @@ -425,7 +458,7 @@ msgstr "" msgid "Incoming mails" msgstr "" -#: ./browser/settings.py:1154 +#: ./browser/settings.py:1186 msgid "Incoming mails folder period (month, week, day)" msgstr "" @@ -437,10 +470,34 @@ msgstr "" msgid "Intro template" msgstr "" +#: ./browser/views.py:108 +msgid "Keep DMS files" +msgstr "" + +#: ./browser/views.py:115 +msgid "Keep annexes" +msgstr "" + +#: ./browser/views.py:87 +msgid "Keep classification category" +msgstr "" + +#: ./browser/views.py:94 +msgid "Keep classification folder" +msgstr "" + +#: ./browser/views.py:101 +msgid "Keep linked mails" +msgstr "" + #: ./browser/settings.py:280 msgid "Lastname Firstname" msgstr "" +#: ./browser/views.py:122 +msgid "Link to original" +msgstr "" + #: ./subscribers.py:758 msgid "Linked objects: ${list}" msgstr "" @@ -490,7 +547,7 @@ msgstr "" msgid "No incoming mail !" msgstr "" -#: ./browser/settings.py:587 +#: ./browser/settings.py:619 msgid "ONCE ACTIVATED, THIS OPTION CAN'T BE EASILY UNDONE !!
When activating this option, a group encoder function is added in the configuration, a new field is added to the contact form to choose the creating group and permissions are given to the selected creating group. Contacts are then separately handled following the creating groups.
This option can be combined with the mail creating group option." msgstr "" @@ -533,7 +590,7 @@ msgstr "" msgid "Outgoing Date" msgstr "" -#: ./browser/settings.py:539 +#: ./browser/settings.py:571 #: ./setuphandlers.py:2726 msgid "Outgoing email" msgstr "" @@ -554,7 +611,7 @@ msgstr "" msgid "Outgoing mails" msgstr "" -#: ./browser/settings.py:1158 +#: ./browser/settings.py:1190 msgid "Outgoing mails folder period (month, week, day)" msgstr "" @@ -572,7 +629,7 @@ msgstr "" msgid "Please update manually ${type} local roles for creating_group !" msgstr "" -#: ./browser/settings.py:503 +#: ./browser/settings.py:509 msgid "Post mailing" msgstr "" @@ -612,7 +669,7 @@ msgstr "" msgid "Reply to ${ref}" msgstr "" -#: ./browser/settings.py:474 +#: ./browser/settings.py:480 msgid "Response prefix" msgstr "" @@ -632,11 +689,11 @@ msgstr "" msgid "Send email" msgstr "" -#: ./browser/settings.py:558 +#: ./browser/settings.py:590 msgid "Send email with sender as reply to" msgstr "" -#: ./browser/settings.py:493 +#: ./browser/settings.py:499 #: ./dmsmail.py:697 msgid "Send modes" msgstr "" @@ -654,7 +711,7 @@ msgstr "" msgid "Sender held position service email is used" msgstr "" -#: ./browser/settings.py:478 +#: ./browser/settings.py:484 msgid "Sender list is sorted on firstname" msgstr "" @@ -690,7 +747,7 @@ msgstr "" msgid "Style template" msgstr "" -#: ./browser/settings.py:576 +#: ./browser/settings.py:608 msgid "TAL compliant with variables: view, context, user, dghv, sender, request and modules." msgstr "" @@ -752,7 +809,7 @@ msgstr "" msgid "Types of incoming mail" msgstr "" -#: ./browser/settings.py:460 +#: ./browser/settings.py:466 msgid "Types of outgoing mail" msgstr "" @@ -764,11 +821,11 @@ msgstr "" msgid "Update preview" msgstr "" -#: ./browser/settings.py:487 +#: ./browser/settings.py:493 msgid "User fullname used format" msgstr "" -#: ./browser/settings.py:613 +#: ./browser/settings.py:645 msgid "Users hidden in dashboards filter" msgstr "" @@ -828,11 +885,11 @@ msgstr "" msgid "You must select an assigned user before you can propose to an agent !" msgstr "" -#: ./browser/views.py:271 +#: ./browser/views.py:382 msgid "Your email has been sent." msgstr "" -#: ./browser/views.py:243 +#: ./browser/views.py:354 msgid "Your email has not been sent: ${error}." msgstr "" diff --git a/imio/dms/mail/skins/imio_dms_mail/imiodmsmail.css.dtml b/imio/dms/mail/skins/imio_dms_mail/imiodmsmail.css.dtml index fe4c3b6b..c8e51504 100644 --- a/imio/dms/mail/skins/imio_dms_mail/imiodmsmail.css.dtml +++ b/imio/dms/mail/skins/imio_dms_mail/imiodmsmail.css.dtml @@ -471,6 +471,10 @@ dl.portletWidgetCollection div.portlet_add_icons a { background-image: url('&dtml-portal_url;/++resource++imio.dms.mail/models_icon.png'); } +.apButtonAction_duplicate { + background-image: url('&dtml-portal_url;/++resource++imio.dms.mail/copy.svg'); +} + .apButtonAction_sendemail { background-image: url('&dtml-portal_url;/++resource++imio.dms.mail/send_email.svg'); background-size: 18px 18px; diff --git a/imio/dms/mail/tests/test_views.py b/imio/dms/mail/tests/test_views.py index 71192b35..0d0062c9 100644 --- a/imio/dms/mail/tests/test_views.py +++ b/imio/dms/mail/tests/test_views.py @@ -7,11 +7,19 @@ from imio.dms.mail.browser.views import parse_query from imio.dms.mail.testing import change_user from imio.dms.mail.testing import DMSMAIL_INTEGRATION_TESTING +from imio.dms.mail.utils import sub_create from imio.helpers.content import get_object from imio.helpers.content import richtextval from imio.helpers.emailer import get_mail_host +from imio.helpers.test_helpers import ImioTestHelpers +from mock import patch from plone import api +from plone.dexterity.utils import createContentInContainer +from plone.namedfile import NamedBlobFile +from z3c.relationfield.relation import RelationValue +from zope.component import getUtility from zope.i18n import translate +from zope.intid.interfaces import IIntIds import json import unittest @@ -297,3 +305,177 @@ def test_call(self): self.assertIn(u">012/34.56.79<", rendered) self.assertIn(u">Rue Léon Morel, 1<", rendered) self.assertIn(u">5032 Isnes<", rendered) + +class TestDuplicate(unittest.TestCase, ImioTestHelpers): + """ + Test the duplication of outgoing mails. + """ + + layer = DMSMAIL_INTEGRATION_TESTING + + def setUp(self): + self.portal = self.layer["portal"] + self.request = self.portal.REQUEST + self.pc = api.portal.get_tool('portal_catalog') + self.change_user("siteadmin") + self.intids = getUtility(IIntIds) + + self.omail2 = sub_create( + self.portal["outgoing-mail"], + "dmsoutgoingmail", + datetime.now(), + "my-id2", + title="My title", + description="Description", + send_modes=["post"], + classification_folders=[self.portal.folders['ordre-public-reglement-general-de-police'].UID()], + classification_categories=self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories, + ) + + self.omail = sub_create( + self.portal["outgoing-mail"], + "dmsoutgoingmail", + datetime.now(), + "my-id", + title="My title", + description="Description", + send_modes=["post"], + classification_folders=[self.portal.folders['ordre-public-reglement-general-de-police'].UID()], + classification_categories=self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories, + reply_to=[RelationValue(self.intids.getId(self.omail2))], + ) + self.dmsommainfile = createContentInContainer(self.omail, "dmsommainfile", title=u"D001", file=NamedBlobFile(filename=u"scanned.pdf")) + self.dmsappendixfile = createContentInContainer( + self.omail, "dmsappendixfile", title=u"A001", file=NamedBlobFile(filename=u"appendix.odt") + ) + self.form = self.omail.restrictedTraverse("@@duplicate") + + @patch("imio.dms.mail.browser.views.DuplicateForm.extractData") + def test_duplicate(self, extractData): + extractData.return_value = { + 'keep_category': True, + 'keep_folder': True, + 'keep_linked_mails': True, + 'keep_dms_files': True, + 'keep_annexes': True, + 'link_to_original': True, + }, None + self.form.handleApply(self.form, "duplicate") + + brains = self.pc(portal_type="dmsoutgoingmail", id="copy_of_my-id") + self.assertEqual(len(brains), 1) + duplicated_mail = brains[0].getObject() + self.assertEqual(duplicated_mail.title, u"My title") + self.assertEqual(duplicated_mail.description, u"Description") + self.assertEqual(duplicated_mail.send_modes, ["post"]) + self.assertGreater(duplicated_mail.creation_date, self.omail.creation_date) + self.assertNotEqual(duplicated_mail.internal_reference_no, self.omail.internal_reference_no) + self.assertIsNone(duplicated_mail.mail_date) + self.assertIsNone(duplicated_mail.due_date) + self.assertIsNone(duplicated_mail.outgoing_date) + + # Test keep_category + self.assertEqual(duplicated_mail.classification_categories, self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories) + # Test keep_folder + self.assertEqual(duplicated_mail.classification_folders, [self.portal.folders['ordre-public-reglement-general-de-police'].UID()]) + # Test keep_linked_mails + self.assertEqual(len(duplicated_mail.reply_to), 2) + self.assertEqual(self.omail2, duplicated_mail.reply_to[0].to_object) + # Test keep_dms_files + self.assertIn("d001", duplicated_mail) + # Test keep_annexes + self.assertIn("a001", duplicated_mail) + # Test link_to_original + self.assertEqual(self.omail, duplicated_mail.reply_to[1].to_object) + + # Test not keeping categories + extractData.return_value = { + 'keep_category': False, + 'keep_folder': True, + 'keep_linked_mails': True, + 'keep_dms_files': True, + 'keep_annexes': True, + 'link_to_original': True, + }, None + self.form.handleApply(self.form, "duplicate") + brains = self.pc(portal_type="dmsoutgoingmail", id="copy2_of_my-id") + self.assertEqual(len(brains), 1) + duplicated_mail = brains[0].getObject() + self.assertIsNone(duplicated_mail.classification_categories) + + # Test not keeping folders + extractData.return_value = { + 'keep_category': True, + 'keep_folder': False, + 'keep_linked_mails': True, + 'keep_dms_files': True, + 'keep_annexes': True, + 'link_to_original': True, + }, None + self.form.handleApply(self.form, "duplicate") + brains = self.pc(portal_type="dmsoutgoingmail", id="copy3_of_my-id") + self.assertEqual(len(brains), 1) + duplicated_mail = brains[0].getObject() + self.assertIsNone(duplicated_mail.classification_folders) + + # Test not keeping linked mails + extractData.return_value = { + 'keep_category': True, + 'keep_folder': True, + 'keep_linked_mails': False, + 'keep_dms_files': True, + 'keep_annexes': True, + 'link_to_original': True, + }, None + self.form.handleApply(self.form, "duplicate") + brains = self.pc(portal_type="dmsoutgoingmail", id="copy4_of_my-id") + self.assertEqual(len(brains), 1) + duplicated_mail = brains[0].getObject() + self.assertEqual(len(duplicated_mail.reply_to), 1) + self.assertNotEqual(self.omail2, duplicated_mail.reply_to[0].to_object) + + # Test not keeping dms files + extractData.return_value = { + 'keep_category': True, + 'keep_folder': True, + 'keep_linked_mails': True, + 'keep_dms_files': False, + 'keep_annexes': True, + 'link_to_original': True, + }, None + self.form.handleApply(self.form, "duplicate") + brains = self.pc(portal_type="dmsoutgoingmail", id="copy5_of_my-id") + self.assertEqual(len(brains), 1) + duplicated_mail = brains[0].getObject() + self.assertNotIn("d001", duplicated_mail) + + # Test not keeping annexes + extractData.return_value = { + 'keep_category': True, + 'keep_folder': True, + 'keep_linked_mails': True, + 'keep_dms_files': True, + 'keep_annexes': False, + 'link_to_original': True, + }, None + self.form.handleApply(self.form, "duplicate") + brains = self.pc(portal_type="dmsoutgoingmail", id="copy6_of_my-id") + self.assertEqual(len(brains), 1) + duplicated_mail = brains[0].getObject() + self.assertNotIn("a001", duplicated_mail) + + # Test not linking to original + extractData.return_value = { + 'keep_category': True, + 'keep_folder': True, + 'keep_linked_mails': True, + 'keep_dms_files': True, + 'keep_annexes': True, + 'link_to_original': False, + }, None + self.form.handleApply(self.form, "duplicate") + brains = self.pc(portal_type="dmsoutgoingmail", id="copy7_of_my-id") + self.assertEqual(len(brains), 1) + duplicated_mail = brains[0].getObject() + self.assertEqual(len(duplicated_mail.reply_to), 1) + self.assertNotEqual(self.omail, duplicated_mail.reply_to[0].to_object) From ebda3f95c3efa89221a81e3918073137fb001c85 Mon Sep 17 00:00:00 2001 From: Chris Adam Date: Tue, 5 Aug 2025 16:29:15 +0200 Subject: [PATCH 03/10] Renamed duplicate form to om-duplicate --- imio/dms/mail/browser/actionspanel.py | 5 +- imio/dms/mail/browser/configure.zcml | 4 +- .../templates/actions_panel_duplicate.pt | 2 +- imio/dms/mail/browser/views.py | 103 +++++++++++------- 4 files changed, 72 insertions(+), 42 deletions(-) diff --git a/imio/dms/mail/browser/actionspanel.py b/imio/dms/mail/browser/actionspanel.py index 3099892f..312068e3 100644 --- a/imio/dms/mail/browser/actionspanel.py +++ b/imio/dms/mail/browser/actionspanel.py @@ -212,8 +212,9 @@ def may_duplicate(self): """ Method that check if special 'duplicate' action has to be displayed. """ - # TODO define permissions for duplicate - return True + if self.member.has_permission("Add portal content", self.context): + return True + return False def render_duplicate_button(self): if self.may_duplicate(): diff --git a/imio/dms/mail/browser/configure.zcml b/imio/dms/mail/browser/configure.zcml index dad1eefd..3a8bad4b 100644 --- a/imio/dms/mail/browser/configure.zcml +++ b/imio/dms/mail/browser/configure.zcml @@ -19,10 +19,10 @@ /> - diff --git a/imio/dms/mail/browser/views.py b/imio/dms/mail/browser/views.py index a1fcd618..f39feb91 100644 --- a/imio/dms/mail/browser/views.py +++ b/imio/dms/mail/browser/views.py @@ -14,6 +14,7 @@ from imio.dms.mail.browser.table import PersonnelTable from imio.dms.mail.dmsfile import IImioDmsFile from imio.dms.mail.interfaces import IPersonnelContact +from imio.dms.mail.utils import sub_create from imio.helpers.content import richtextval from imio.helpers.content import uuidToObject from imio.helpers.emailer import add_attachment @@ -83,7 +84,7 @@ def redirect_url(self, uid): return "{}/persistent-document-generation?{}".format(url, "&".join(params)) -class IDuplicateFormSchema(model.Schema): +class IOMDuplicateFormSchema(model.Schema): keep_category = schema.Bool( title=_(u"Keep classification category"), @@ -122,11 +123,11 @@ class IDuplicateFormSchema(model.Schema): ) -class DuplicateForm(Form): +class OMDuplicateForm(Form): """Duplicate an outgoing mail.""" label = _(u"Duplicate mail") - fields = Fields(IDuplicateFormSchema) + fields = Fields(IOMDuplicateFormSchema) ignoreContext = True @button.buttonAndHandler(_('Duplicate'), name='duplicate') @@ -138,39 +139,65 @@ def handleApply(self, action): return # Duplicate the mail - parent = self.context.aq_parent - clipboard = parent.manage_copyObjects([self.context.getId()]) - result = parent.manage_pasteObjects(clipboard) - duplicated_mail = parent[result[0]['new_id']] - duplicated_mail.creation_date = DateTime() - duplicated_mail.reindexObject(idxs=['created']) - duplicated_mail.internal_reference_no = internalReferenceOutgoingMailDefaultValue(self) - duplicated_mail.due_date = None - duplicated_mail.outgoing_date = None - duplicated_mail.mail_date = None - - if not data['keep_category']: - duplicated_mail.classification_categories = None - - if not data['keep_folder']: - duplicated_mail.classification_folders = None - - if not data['keep_linked_mails']: - duplicated_mail.reply_to = None - - if not data['keep_dms_files']: - dms_files = [sub_content.getId() for sub_content in duplicated_mail.values() if IImioDmsFile.providedBy(sub_content)] + # duplicated_mail.creation_date = DateTime() + # duplicated_mail.reindexObject(idxs=['created']) + # duplicated_mail.internal_reference_no = internalReferenceOutgoingMailDefaultValue(self) + # duplicated_mail.due_date = None + # duplicated_mail.outgoing_date = None + # duplicated_mail.mail_date = None + original_mail = self.context + i = 0 + while True: + try: + print(i) + duplicated_mail = sub_create( + api.portal.get()["outgoing-mail"], + "dmsoutgoingmail", + datetime.now(), + "my-id-%s" % i, # TODO use a better id + title=original_mail.title, + description=original_mail.description, + recipients=original_mail.recipients[:] if original_mail.recipients else None, + treating_groups=original_mail.treating_groups[:] if original_mail.treating_groups else None, + assigned_user= original_mail.assigned_user, + sender=original_mail.sender, + recipient_groups=original_mail.recipient_groups[:] if original_mail.recipient_groups else None, + send_modes= original_mail.send_modes[:] if original_mail.send_modes else None, + task_description=original_mail.task_description, + ) + except Exception as e: + i += 1 + if i > 1000: + raise e + else: + break + + if data['keep_category'] and hasattr(original_mail, 'classification_categories') and original_mail.classification_categories: + duplicated_mail.classification_categories = original_mail.classification_categories[:] + + if data['keep_folder'] and hasattr(original_mail, 'classification_folders') and original_mail.classification_folders: + duplicated_mail.classification_folders = original_mail.classification_folders[:] + + if data['keep_linked_mails'] and hasattr(original_mail, 'reply_to') and original_mail.reply_to: + duplicated_mail.reply_to = original_mail.reply_to[:] + + if data['keep_dms_files']: + # FIXME do not use clipboard + dms_files = [sub_content.getId() for sub_content in original_mail.values() if IImioDmsFile.providedBy(sub_content)] + import ipdb; ipdb.set_trace() if dms_files: - duplicated_mail.manage_delObjects(dms_files) + clipboard = original_mail.manage_copyObjects(dms_files) + duplicated_mail.manage_pasteObjects(clipboard) - if not data['keep_annexes']: - annexes = [sub_content.getId() for sub_content in duplicated_mail.values() if IDmsAppendixFile.providedBy(sub_content)] + if data['keep_annexes']: + annexes = [sub_content.getId() for sub_content in original_mail.values() if IDmsAppendixFile.providedBy(sub_content)] if annexes: - duplicated_mail.manage_delObjects(annexes) + clipboard = original_mail.manage_copyObjects(annexes) + duplicated_mail.manage_pasteObjects(clipboard) if data['link_to_original']: intids = getUtility(IIntIds) - rel_id = intids.getId(self.context) + rel_id = intids.getId(original_mail) if duplicated_mail.reply_to is None: duplicated_mail.reply_to = [] duplicated_mail.reply_to.append(RelationValue(rel_id)) @@ -178,21 +205,23 @@ def handleApply(self, action): self.request.response.redirect(duplicated_mail.absolute_url()+"/edit") def updateWidgets(self): - super(DuplicateForm, self).updateWidgets() - self.widgets["keep_category"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_category", IImioDmsMailConfig, True) else [] - self.widgets["keep_folder"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_folder", IImioDmsMailConfig, True) else [] - self.widgets["keep_linked_mails"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_linked_mails", IImioDmsMailConfig, True) else [] - self.widgets["keep_dms_files"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_dms_files", IImioDmsMailConfig, True) else [] - self.widgets["keep_annexes"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_annexes", IImioDmsMailConfig, True) else [] - self.widgets["link_to_original"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_link_to_original", IImioDmsMailConfig, True) else [] + super(OMDuplicateForm, self).updateWidgets() navtree_props = getToolByName(api.portal.get(), 'portal_properties').navtree_properties excluded_types = navtree_props.getProperty('metaTypesNotToList', ()) if 'ClassificationContainer' in excluded_types: self.widgets["keep_category"].mode = "hidden" + else: + self.widgets["keep_category"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_category", IImioDmsMailConfig, True) else [] if 'ClassificationFolders' in excluded_types: self.widgets["keep_folder"].mode = "hidden" + else: + self.widgets["keep_folder"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_folder", IImioDmsMailConfig, True) else [] + self.widgets["keep_linked_mails"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_linked_mails", IImioDmsMailConfig, True) else [] + self.widgets["keep_dms_files"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_dms_files", IImioDmsMailConfig, True) else [] + self.widgets["keep_annexes"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_annexes", IImioDmsMailConfig, True) else [] + self.widgets["link_to_original"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_link_to_original", IImioDmsMailConfig, True) else [] def parse_query(text): """Copied from plone.app.vocabularies.catalog.parse_query but cleaned.""" From 02da91f764c3fb2ca5d78a8674a5e129ef51d95d Mon Sep 17 00:00:00 2001 From: Stephan Geulette Date: Thu, 7 Aug 2025 11:12:01 +0200 Subject: [PATCH 04/10] Restored previous pot and po files --- .../locales/en/LC_MESSAGES/imio.dms.mail.po | 57 --- .../locales/fr/LC_MESSAGES/imio.dms.mail.po | 57 --- imio/dms/mail/locales/imio.dms.mail.pot | 380 +++++++++++++----- 3 files changed, 279 insertions(+), 215 deletions(-) diff --git a/imio/dms/mail/locales/en/LC_MESSAGES/imio.dms.mail.po b/imio/dms/mail/locales/en/LC_MESSAGES/imio.dms.mail.po index b695fac6..6a596752 100644 --- a/imio/dms/mail/locales/en/LC_MESSAGES/imio.dms.mail.po +++ b/imio/dms/mail/locales/en/LC_MESSAGES/imio.dms.mail.po @@ -1330,60 +1330,3 @@ msgstr "Templates" #: ./browser/viewlets.py:107 msgid "zip_code" msgstr "" - -#: ./browser/settings.py:556 -msgid "Default value when duplicating an outgoing mail for 'Keep DMS files'" -msgstr "" - -#: ./browser/settings.py:560 -msgid "Default value when duplicating an outgoing mail for 'Keep annexes'" -msgstr "" - -#: ./browser/settings.py:544 -msgid "Default value when duplicating an outgoing mail for 'Keep classification category'" -msgstr "" - -#: ./browser/settings.py:548 -msgid "Default value when duplicating an outgoing mail for 'Keep classification folder'" -msgstr "" - -#: ./browser/settings.py:552 -msgid "Default value when duplicating an outgoing mail for 'Keep linked mails'" -msgstr "" - -#: ./browser/settings.py:564 -msgid "Default value when duplicating an outgoing mail for 'Link to original'" -msgstr "" - -#: ./browser/views.py:108 -msgid "Keep DMS files" -msgstr "" - -#: ./browser/views.py:115 -msgid "Keep annexes" -msgstr "" - -#: ./browser/views.py:87 -msgid "Keep classification category" -msgstr "" - -#: ./browser/views.py:94 -msgid "Keep classification folder" -msgstr "" - -#: ./browser/views.py:101 -msgid "Keep linked mails" -msgstr "" - -#: ./browser/views.py:122 -msgid "Link to original" -msgstr "" - -#: ./browser/templates/actions_panel_duplicate.pt:6 -#: ./browser/views.py:136 -msgid "Duplicate" -msgstr "" - -#: ./browser/views.py:132 -msgid "Duplicate mail" -msgstr "" diff --git a/imio/dms/mail/locales/fr/LC_MESSAGES/imio.dms.mail.po b/imio/dms/mail/locales/fr/LC_MESSAGES/imio.dms.mail.po index e8f16824..538b2fd8 100644 --- a/imio/dms/mail/locales/fr/LC_MESSAGES/imio.dms.mail.po +++ b/imio/dms/mail/locales/fr/LC_MESSAGES/imio.dms.mail.po @@ -1332,60 +1332,3 @@ msgstr "Modèles" #: ./browser/viewlets.py:107 msgid "zip_code" msgstr "Code postal" - -#: ./browser/settings.py:556 -msgid "Default value when duplicating an outgoing mail for 'Keep DMS files'" -msgstr "Valeur par défaut pour le champ 'Garder les fichiers GED' lorsqu'on duplique un courrier sortant" - -#: ./browser/settings.py:560 -msgid "Default value when duplicating an outgoing mail for 'Keep annexes'" -msgstr "Valeur par défaut pour le champ 'Garder les annexes' lorsqu'on duplique un courrier sortant" - -#: ./browser/settings.py:544 -msgid "Default value when duplicating an outgoing mail for 'Keep classification category'" -msgstr "Valeur par défaut pour le champ 'Garder les catégories' lorsqu'on duplique un courrier sortant" - -#: ./browser/settings.py:548 -msgid "Default value when duplicating an outgoing mail for 'Keep classification folder'" -msgstr "Valeur par défaut pour le champ 'Garder les dossiers' lorsqu'on duplique un courrier sortant" - -#: ./browser/settings.py:552 -msgid "Default value when duplicating an outgoing mail for 'Keep linked mails'" -msgstr "Valeur par défaut pour le champ 'Garder les courriers liés' lorsqu'on duplique un courrier sortant" - -#: ./browser/settings.py:564 -msgid "Default value when duplicating an outgoing mail for 'Link to original'" -msgstr "Valeur par défaut pour le champ 'Lier le courrier dupliqué à l'original' lorsqu'on duplique un courrier sortant" - -#: ./browser/views.py:108 -msgid "Keep DMS files" -msgstr "Garder les fichiers GED" - -#: ./browser/views.py:115 -msgid "Keep annexes" -msgstr "Garder les annexes" - -#: ./browser/views.py:87 -msgid "Keep classification category" -msgstr "Garder les catégories" - -#: ./browser/views.py:94 -msgid "Keep classification folder" -msgstr "Garder les dossiers" - -#: ./browser/views.py:101 -msgid "Keep linked mails" -msgstr "Garder les courriers liés" - -#: ./browser/views.py:122 -msgid "Link to original" -msgstr "Lier le courrier dupliqué à l'original" - -#: ./browser/templates/actions_panel_duplicate.pt:6 -#: ./browser/views.py:136 -msgid "Duplicate" -msgstr "Dupliquer" - -#: ./browser/views.py:132 -msgid "Duplicate mail" -msgstr "Dupliquer le courrier" diff --git a/imio/dms/mail/locales/imio.dms.mail.pot b/imio/dms/mail/locales/imio.dms.mail.pot index f318b6b7..e0bbe8af 100644 --- a/imio/dms/mail/locales/imio.dms.mail.pot +++ b/imio/dms/mail/locales/imio.dms.mail.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2025-07-31 15:22+0000\n" +"POT-Creation-Date: 2025-05-18 07:19+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,31 +17,35 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: imio.dms.mail\n" -#: ./browser/settings.py:687 +#: ./vocabularies.py +msgid "${element_title} (Inactive)" +msgstr "" + +#: ./browser/settings.py:655 msgid "${tab} tab: multiple value '${value}' in '${field}' setting !! Must be unique" msgstr "" -#: ./browser/settings.py:769 +#: ./browser/settings.py:737 msgid "${tab} tab: send_modes field must have values starting with « post », « email » or « other »" msgstr "" -#: ./browser/settings.py:705 +#: ./browser/settings.py:673 msgid "${tab} tab: unchecking '${field}' setting is not expected !!" msgstr "" -#: ./browser/settings.py:728 +#: ./browser/settings.py:696 msgid "${tab} tab: « ${field} » rule ${rule} has an invalid pattern in « ${col} »" msgstr "" -#: ./browser/settings.py:749 +#: ./browser/settings.py:717 msgid "${tab} tab: « ${field} » rule ${rule} is configured with an assigned user « ${user} » not in the corresponding treating group « ${tg} »" msgstr "" -#: ./browser/settings.py:736 +#: ./browser/settings.py:704 msgid "${tab} tab: « ${field} » rule ${rule} is configured with no values defined" msgstr "" -#: ./browser/views.py:56 +#: ./browser/views.py:44 msgid "${title}: create from template" msgstr "" @@ -57,11 +61,27 @@ msgstr "" msgid "${type} state set" msgstr "" +#: ./browser/settings.py +msgid "'${field}': position ${position}" +msgstr "" + +#: ./__init__.py:26 +msgid "++resource++imio.dms.mail/wf_again.png" +msgstr "" + +#: ./__init__.py:25 +msgid "++resource++imio.dms.mail/wf_back.png" +msgstr "" + #: ./subscribers.py:731 msgid "Linked groups : ${list}" msgstr "" -#: ./browser/settings.py:615 +#: ./browser/settings.py:422 +msgid "Unconfigured fields are: ${list}" +msgstr "" + +#: ./browser/settings.py:583 msgid "A user can see all mail titles linked to a contact." msgstr "" @@ -101,10 +121,18 @@ msgstr "" msgid "All contacts export" msgstr "" +#: ./browser/views.py:89 +msgid "All under" +msgstr "" + #: ./browser/batchactions.py:69 msgid "An assigned user is not in this new treating group. Mail \"${mail}\" !" msgstr "" +#: ./browser/batchactions.py:331 +msgid "Apply" +msgstr "" + #: ./browser/templates/actions_panel_assign_user.pt:11 msgid "Assign" msgstr "" @@ -129,6 +157,10 @@ msgstr "" msgid "Base template" msgstr "" +#: ./browser/batchactions.py:51 +msgid "Batch action form" +msgstr "" + #: ./browser/batchactions.py:218 msgid "Batch copy to" msgstr "" @@ -161,19 +193,23 @@ msgstr "" msgid "Batch sender contact field change" msgstr "" +#: ./browser/batchactions.py:134 +msgid "Batch state change" +msgstr "" + #: ./browser/batchactions.py:41 msgid "Batch treating group change" msgstr "" -#: ./browser/views.py:354 +#: ./browser/views.py:242 msgid "Cannot use localhost as smtp" msgstr "" -#: ./browser/settings.py:584 +#: ./browser/settings.py:552 msgid "Check if a service encoder can edit his service email templates." msgstr "" -#: ./browser/settings.py:488 +#: ./browser/settings.py:482 msgid "Check if a service encoder can edit his service office templates." msgstr "" @@ -190,14 +226,22 @@ msgstr "" msgid "Choose a value !" msgstr "" -#: ./browser/views.py:61 +#: ./browser/views.py:49 msgid "Choose this template" msgstr "" -#: ./browser/settings.py:588 +#: ./browser/settings.py:373 +msgid "Choose to which state a manually forwarded email will go." +msgstr "" + +#: ./browser/settings.py:556 msgid "Close outgoing mail on email send" msgstr "" +#: ./browser/batchactions.py:148 +msgid "Comment" +msgstr "" + #: ./setuphandlers.py:2453 msgid "Common templates" msgstr "" @@ -220,7 +264,7 @@ msgstr "" msgid "Contact lists" msgstr "" -#: ./browser/settings.py:613 +#: ./browser/settings.py:581 msgid "Contacts" msgstr "" @@ -228,6 +272,10 @@ msgstr "" msgid "Contacts audit" msgstr "" +#: ./setuphandlers.py:1836 +msgid "Contacts export" +msgstr "" + #: ./browser/templates/actions_panel_create_from_template.pt:6 msgid "Create from template" msgstr "" @@ -240,7 +288,7 @@ msgstr "" msgid "Creating groups : to be used in kofax index" msgstr "" -#: ./browser/settings.py:1194 +#: ./browser/settings.py:1161 msgid "Current product version" msgstr "" @@ -252,34 +300,10 @@ msgstr "" msgid "Data transfer" msgstr "" -#: ./browser/settings.py:599 +#: ./browser/settings.py:567 msgid "Default bcc emails" msgstr "" -#: ./browser/settings.py:556 -msgid "Default value when duplicating an outgoing mail for 'Keep DMS files'" -msgstr "" - -#: ./browser/settings.py:560 -msgid "Default value when duplicating an outgoing mail for 'Keep annexes'" -msgstr "" - -#: ./browser/settings.py:544 -msgid "Default value when duplicating an outgoing mail for 'Keep classification category'" -msgstr "" - -#: ./browser/settings.py:548 -msgid "Default value when duplicating an outgoing mail for 'Keep classification folder'" -msgstr "" - -#: ./browser/settings.py:552 -msgid "Default value when duplicating an outgoing mail for 'Keep linked mails'" -msgstr "" - -#: ./browser/settings.py:564 -msgid "Default value when duplicating an outgoing mail for 'Link to original'" -msgstr "" - #: ./browser/settings.py:315 msgid "Displayed title" msgstr "" @@ -292,19 +316,19 @@ msgstr "" msgid "Dms Mail Data Transfer" msgstr "" -#: ./browser/settings.py:482 +#: ./browser/settings.py:476 msgid "Dms file must be an odt format" msgstr "" -#: ./browser/settings.py:510 +#: ./browser/settings.py:504 msgid "Do mailing for each postal sending type." msgstr "" -#: ./browser/settings.py:1182 +#: ./browser/settings.py:1149 msgid "Document viewer preservation date" msgstr "" -#: ./browser/settings.py:1178 +#: ./browser/settings.py:1145 msgid "Document viewer preservation days number" msgstr "" @@ -312,28 +336,31 @@ msgstr "" msgid "Due date extension" msgstr "" -#: ./browser/templates/actions_panel_duplicate.pt:6 -#: ./browser/views.py:136 -msgid "Duplicate" +#: ./setuphandlers.py:2746 +msgid "Email general template" msgstr "" -#: ./browser/views.py:132 -msgid "Duplicate mail" +#: ./browser/settings.py:372 +msgid "Email manual forward transition" msgstr "" -#: ./setuphandlers.py:2746 -msgid "Email general template" +#: ./browser/views.py:216 +msgid "Email sent at ${date_hour}." msgstr "" -#: ./browser/settings.py:607 +#: ./browser/settings.py:575 msgid "Email signature model" msgstr "" -#: ./browser/settings.py:583 +#: ./vocabularies.py:135 +msgid "Empty value" +msgstr "" + +#: ./browser/settings.py:551 msgid "Enable edition of service email templates for encoder" msgstr "" -#: ./browser/settings.py:487 +#: ./browser/settings.py:481 msgid "Enable edition of service office templates for encoder" msgstr "" @@ -394,7 +421,7 @@ msgstr "" msgid "From primary organization" msgstr "" -#: ./browser/settings.py:593 +#: ./browser/settings.py:561 msgid "From where to get sender default email" msgstr "" @@ -402,14 +429,18 @@ msgstr "" msgid "Full documentation" msgstr "" -#: ./browser/settings.py:632 +#: ./browser/settings.py:600 msgid "General config tab" msgstr "" -#: ./browser/settings.py:637 +#: ./browser/settings.py:605 msgid "Groups hidden in dashboards filter" msgstr "" +#: ./adapters.py: +msgid "Has response icon" +msgstr "" + #: ./setuphandlers.py:2600 msgid "Header template" msgstr "" @@ -458,7 +489,7 @@ msgstr "" msgid "Incoming mails" msgstr "" -#: ./browser/settings.py:1186 +#: ./browser/settings.py:1153 msgid "Incoming mails folder period (month, week, day)" msgstr "" @@ -470,34 +501,10 @@ msgstr "" msgid "Intro template" msgstr "" -#: ./browser/views.py:108 -msgid "Keep DMS files" -msgstr "" - -#: ./browser/views.py:115 -msgid "Keep annexes" -msgstr "" - -#: ./browser/views.py:87 -msgid "Keep classification category" -msgstr "" - -#: ./browser/views.py:94 -msgid "Keep classification folder" -msgstr "" - -#: ./browser/views.py:101 -msgid "Keep linked mails" -msgstr "" - #: ./browser/settings.py:280 msgid "Lastname Firstname" msgstr "" -#: ./browser/views.py:122 -msgid "Link to original" -msgstr "" - #: ./subscribers.py:758 msgid "Linked objects: ${list}" msgstr "" @@ -523,6 +530,10 @@ msgstr "" msgid "Mail type" msgstr "" +#: ./browser/table.py:75 +msgid "Mailing" +msgstr "" + #: ./setuphandlers.py:2624 msgid "Mailing template" msgstr "" @@ -531,6 +542,10 @@ msgstr "" msgid "Member Area" msgstr "" +#: ./browser/settings.py:460 +msgid "Missing mandatory fields: ${msg}" +msgstr "" + #: ./browser/templates/actions_panel_folder_annexes.pt:5 msgid "Multiple annexes" msgstr "" @@ -539,6 +554,14 @@ msgstr "" msgid "No check" msgstr "" +#: ./browser/batchactions.py:144 +msgid "No common or available transition. Modify your selection." +msgstr "" + +#: ./browser/batchactions.py:193 +msgid "No common or available treating group, or no available assigned user. Modify your selection." +msgstr "" + #: ./browser/batchactions.py:241 msgid "No folder available where you can add templates." msgstr "" @@ -547,7 +570,7 @@ msgstr "" msgid "No incoming mail !" msgstr "" -#: ./browser/settings.py:619 +#: ./browser/settings.py:587 msgid "ONCE ACTIVATED, THIS OPTION CAN'T BE EASILY UNDONE !!
When activating this option, a group encoder function is added in the configuration, a new field is added to the contact form to choose the creating group and permissions are given to the selected creating group. Contacts are then separately handled following the creating groups.
This option can be combined with the mail creating group option." msgstr "" @@ -559,6 +582,10 @@ msgstr "" msgid "Once created and used, value doesn't be changed anymore. None can be used for a 'choose' value." msgstr "" +#: ./browser/batchactions.py:149 +msgid "Optional comment to display in history" +msgstr "" + #: ./migrations/migrate_to_2_1.py:390 #: ./setuphandlers.py:355 msgid "Organizations" @@ -590,7 +617,7 @@ msgstr "" msgid "Outgoing Date" msgstr "" -#: ./browser/settings.py:571 +#: ./browser/settings.py:539 #: ./setuphandlers.py:2726 msgid "Outgoing email" msgstr "" @@ -611,7 +638,7 @@ msgstr "" msgid "Outgoing mails" msgstr "" -#: ./browser/settings.py:1190 +#: ./browser/settings.py:1157 msgid "Outgoing mails folder period (month, week, day)" msgstr "" @@ -625,11 +652,19 @@ msgstr "" msgid "Persons searches" msgstr "" +#: ./profiles/default/registry.xml +msgid "Pipeline to use to import contacts" +msgstr "" + #: ./wfadaptations.py:385 msgid "Please update manually ${type} local roles for creating_group !" msgstr "" -#: ./browser/settings.py:509 +#: ./browser/settings.py:466 +msgid "Position required for fields: ${msg}" +msgstr "" + +#: ./browser/settings.py:503 msgid "Post mailing" msgstr "" @@ -661,6 +696,10 @@ msgstr "" msgid "Relative path" msgstr "" +#: ./adapters.py: +msgid "Remark icon" +msgstr "" + #: ./browser/templates/actions_panel_reply.pt:5 msgid "Reply" msgstr "" @@ -669,7 +708,7 @@ msgstr "" msgid "Reply to ${ref}" msgstr "" -#: ./browser/settings.py:480 +#: ./browser/settings.py:474 msgid "Response prefix" msgstr "" @@ -689,11 +728,11 @@ msgstr "" msgid "Send email" msgstr "" -#: ./browser/settings.py:590 +#: ./browser/settings.py:558 msgid "Send email with sender as reply to" msgstr "" -#: ./browser/settings.py:499 +#: ./browser/settings.py:493 #: ./dmsmail.py:697 msgid "Send modes" msgstr "" @@ -711,7 +750,7 @@ msgstr "" msgid "Sender held position service email is used" msgstr "" -#: ./browser/settings.py:484 +#: ./browser/settings.py:478 msgid "Sender list is sorted on firstname" msgstr "" @@ -747,7 +786,7 @@ msgstr "" msgid "Style template" msgstr "" -#: ./browser/settings.py:608 +#: ./browser/settings.py:576 msgid "TAL compliant with variables: view, context, user, dghv, sender, request and modules." msgstr "" @@ -759,6 +798,10 @@ msgstr "" msgid "TAL condition 2" msgstr "" +#: ./browser/settings.py:257 +msgid "TAL condition 3" +msgstr "" + #: ./migrations/migrate_to_1_0.py:80 #: ./migrations/migrate_to_2_0.py:110 #: ./setuphandlers.py:298 @@ -779,6 +822,10 @@ msgstr "" msgid "The assigned user is not in the selected group !" msgstr "" +#: ./browser/viewlets.py:115 +msgid "This contact '${title}' has missing address fields: ${keys}" +msgstr "" + #: ./wfadaptations.py:53 msgid "This workflow adaptation is only valid for ${workflow} !" msgstr "" @@ -791,6 +838,10 @@ msgstr "" msgid "Transferer" msgstr "" +#: ./browser/batchactions.py:141 +msgid "Transition" +msgstr "" + #: ./browser/settings.py:254 msgid "Treating group value" msgstr "" @@ -809,7 +860,7 @@ msgstr "" msgid "Types of incoming mail" msgstr "" -#: ./browser/settings.py:466 +#: ./browser/settings.py:460 msgid "Types of outgoing mail" msgstr "" @@ -821,11 +872,11 @@ msgstr "" msgid "Update preview" msgstr "" -#: ./browser/settings.py:493 +#: ./browser/settings.py:487 msgid "User fullname used format" msgstr "" -#: ./browser/settings.py:645 +#: ./browser/settings.py:613 msgid "Users hidden in dashboards filter" msgstr "" @@ -885,11 +936,11 @@ msgstr "" msgid "You must select an assigned user before you can propose to an agent !" msgstr "" -#: ./browser/views.py:382 +#: ./browser/views.py:270 msgid "Your email has been sent." msgstr "" -#: ./browser/views.py:354 +#: ./browser/views.py:242 msgid "Your email has not been sent: ${error}." msgstr "" @@ -931,6 +982,18 @@ msgstr "" msgid "backrefs_viewlet_title" msgstr "" +#: ./columns.py:56 +msgid "batch_printable_False" +msgstr "" + +#: ./columns.py:56 +msgid "batch_printable_True" +msgstr "" + +#: ./browser/viewlets.py:107 +msgid "city" +msgstr "" + #: ./setuphandlers.py:127 msgid "classification_tree_tab" msgstr "" @@ -943,6 +1006,14 @@ msgstr "" msgid "create_classification_folder" msgstr "" +#: ./browser/templates/category_contact.pt:11 +msgid "create_contact" +msgstr "" + +#: ./browser/templates/category_contact.pt:11 +msgid "create_contact_list" +msgstr "" + #: ./browser/templates/category_im.pt:17 msgid "create_iem" msgstr "" @@ -951,10 +1022,22 @@ msgstr "" msgid "create_im" msgstr "" +#: ./browser/templates/category_om.pt:17 +msgid "create_oem" +msgstr "" + #: ./browser/templates/category_om.pt:10 msgid "create_om" msgstr "" +#: ./browser/templates/category_contact.pt:11 +msgid "create_organization" +msgstr "" + +#: ./browser/templates/category_contact.pt:11 +msgid "create_person" +msgstr "" + #: ./setuphandlers.py:116 msgid "folders_tab" msgstr "" @@ -963,6 +1046,10 @@ msgstr "" msgid "followed" msgstr "" +#: ./browser/settings.py +msgid "for '${conf}' config => ${fields}. " +msgstr "" + #: ./migrations/migrate_to_2_0.py:251 #: ./migrations/migrate_to_2_1.py:292 #: ./migrations/migrate_to_2_3.py:170 @@ -1019,6 +1106,10 @@ msgstr "" msgid "im_to_validate" msgstr "" +#: ./browser/settings.py +msgid "imail_fields" +msgstr "" + #: ./profiles.zcml:14 msgid "imio.dms.mail" msgstr "" @@ -1047,6 +1138,10 @@ msgstr "" msgid "in_my_group" msgstr "" +#: ./browser/reply_form.py +msgid "incoming mails" +msgstr "" + #: ./setuphandlers.py:231 msgid "incoming_mail_tab" msgstr "" @@ -1056,6 +1151,10 @@ msgstr "" msgid "listing_date_title" msgstr "" +#: ./browser/listing.py:31 +msgid "listing_no_group" +msgstr "" + #. Default: "Object" #: ./browser/templates/listing.pt:31 msgid "listing_object_title" @@ -1066,6 +1165,10 @@ msgstr "" msgid "listing_sender_title" msgstr "" +#: ./browser/viewlets.py:107 +msgid "number" +msgstr "" + #: ./setuphandlers.py:1505 msgid "om_have_treated" msgstr "" @@ -1094,12 +1197,16 @@ msgstr "" msgid "om_to_validate" msgstr "" -#: ./setuphandlers.py:262 -msgid "outgoing_mail_tab" +#: ./browser/settings.py +msgid "omail_fields" msgstr "" -#: ./browser/tabular_view.py:62 -msgid "plone" +#: ./profiles/default/registry.xml +msgid "organization_type_query_field" +msgstr "" + +#: ./setuphandlers.py:262 +msgid "outgoing_mail_tab" msgstr "" #. Default: "You have been redirected here because you do not have access anymore to the element you just edited." @@ -1111,6 +1218,73 @@ msgstr "" msgid "searchfor: ${state}" msgstr "" +#: contacts +msgid "searchfor_active" +msgstr "" + +#: dmsincomingmail +#: task +msgid "searchfor_closed" +msgstr "" + +#: dmsincomingmail +#: dmsoutgoingmail +#: task +msgid "searchfor_created" +msgstr "" + +#: contacts +msgid "searchfor_deactivated" +msgstr "" + +#: task +msgid "searchfor_in_progress" +msgstr "" + +#: dmsincomingmail +msgid "searchfor_in_treatment" +msgstr "" + +#: dmsincomingmail +msgid "searchfor_proposed_to_agent" +msgstr "" + +#: dmsincomingmail +msgid "searchfor_proposed_to_manager" +msgstr "" + +#: task +msgid "searchfor_realized" +msgstr "" + +#: dmsoutgoingmail +msgid "searchfor_scanned" +msgstr "" + +#: dmsoutgoingmail +msgid "searchfor_sent" +msgstr "" + +#: task +msgid "searchfor_to_assign" +msgstr "" + +#: dmsoutgoingmail +msgid "searchfor_to_be_signed" +msgstr "" + +#: task +msgid "searchfor_to_do" +msgstr "" + +#: dmsoutgoingmail +msgid "searchfor_to_print" +msgstr "" + +#: ./browser/viewlets.py:107 +msgid "street" +msgstr "" + #: ./setuphandlers.py:1177 msgid "task_have_treated" msgstr "" @@ -1154,3 +1328,7 @@ msgstr "" #: ./setuphandlers.py:2451 msgid "templates_tab" msgstr "" + +#: ./browser/viewlets.py:107 +msgid "zip_code" +msgstr "" From 34049ba3837301bc0e705171fa8862cad67d0510 Mon Sep 17 00:00:00 2001 From: Stephan Geulette Date: Thu, 7 Aug 2025 12:20:21 +0200 Subject: [PATCH 05/10] Checked permission on right context --- imio/dms/mail/browser/actionspanel.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/imio/dms/mail/browser/actionspanel.py b/imio/dms/mail/browser/actionspanel.py index 312068e3..fe433d9f 100644 --- a/imio/dms/mail/browser/actionspanel.py +++ b/imio/dms/mail/browser/actionspanel.py @@ -106,7 +106,7 @@ def assignable_users(self): def sortTransitions(self, lst): """Sort transitions following transitions list order""" - lst.sort(lambda x, y: cmp(self.tr_order.get(x["id"], 99), self.tr_order.get(y["id"], 99))) + lst.sort(lambda x, y: cmp(self.tr_order.get(x["id"], 99), self.tr_order.get(y["id"], 99))) # noqa F821 @ram.cache(actionspanelview_cachekey) def DmsIMActionsPanelView__call__( @@ -183,15 +183,15 @@ def __init__(self, context, request): # self.ACCEPTABLE_ACTIONS = ['copy', 'paste', 'delete'] self.ACCEPTABLE_ACTIONS = ["delete"] self.SECTIONS_TO_RENDER += ( - "render_create_from_template_button", "render_duplicate_button", + "render_create_from_template_button", "render_create_new_message", "render_send_email", ) def sortTransitions(self, lst): """Sort transitions following transitions list order""" - lst.sort(lambda x, y: cmp(self.tr_order[x["id"]], self.tr_order[y["id"]])) + lst.sort(lambda x, y: cmp(self.tr_order[x["id"]], self.tr_order[y["id"]])) # noqa F821 def may_create_from_template(self): """ @@ -209,10 +209,8 @@ def render_create_from_template_button(self): return "" def may_duplicate(self): - """ - Method that check if special 'duplicate' action has to be displayed. - """ - if self.member.has_permission("Add portal content", self.context): + """Method that check if special 'duplicate' action has to be displayed.""" + if self.member.has_permission("Add portal content", self.portal["outgoing-mail"]): return True return False @@ -310,7 +308,7 @@ def __init__(self, context, request): def sortTransitions(self, lst): """Sort transitions following transitions list order""" - lst.sort(lambda x, y: cmp(self.tr_order[x["id"]], self.tr_order[y["id"]])) + lst.sort(lambda x, y: cmp(self.tr_order[x["id"]], self.tr_order[y["id"]])) # noqa F821 @ram.cache(actionspanelview_cachekey) def DmsTaskActionsPanelView__call__( From eb10272f01d9691422e1863674bfaccd7366f7a5 Mon Sep 17 00:00:00 2001 From: Stephan Geulette Date: Thu, 7 Aug 2025 12:22:24 +0200 Subject: [PATCH 06/10] Improved settings and fields usage in view --- imio/dms/mail/browser/settings.py | 62 +++++++++++++++---------------- imio/dms/mail/browser/views.py | 52 ++++++++++++++++---------- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/imio/dms/mail/browser/settings.py b/imio/dms/mail/browser/settings.py index a5ed9336..730e97bc 100644 --- a/imio/dms/mail/browser/settings.py +++ b/imio/dms/mail/browser/settings.py @@ -295,6 +295,17 @@ class IStateSetSchema(IRuleSchema): # ] # ) +omail_duplicate_fields = SimpleVocabulary( + [ + SimpleTerm(value=u"category", title=_(u"Keep classification category")), + SimpleTerm(value=u"folder", title=_(u"Keep classification folder")), + SimpleTerm(value=u"reply_to", title=_(u"Keep reply_to mails")), + SimpleTerm(value=u"dms_files", title=_(u"Keep DMS files")), + SimpleTerm(value=u"annexes", title=_(u"Keep annexes")), + SimpleTerm(value=u"link_to_duplicated", title=_(u"Link to duplicated mail")), + ] +) + oemail_sender_email_values = SimpleVocabulary( [ SimpleTerm(value=u"agent_email", title=_(u"Sender held position email is used")), @@ -451,14 +462,10 @@ class IImioDmsMailConfig(model.Schema): "omail_fullname_used_form", "omail_send_modes", "omail_post_mailing", + "omail_duplicate_display_fields", + "omail_duplicate_true_default_values", "omail_fields", "omail_group_encoder", - "omail_duplicate_default_keep_category", - "omail_duplicate_default_keep_folder", - "omail_duplicate_default_keep_linked_mails", - "omail_duplicate_default_keep_dms_files", - "omail_duplicate_default_keep_annexes", - "omail_duplicate_default_link_to_original", ], ) @@ -511,6 +518,22 @@ class IImioDmsMailConfig(model.Schema): default=True, ) + omail_duplicate_display_fields = schema.List( + title=_(u"Fields to display when duplicating an outgoing mail"), + required=False, + value_type=schema.Choice(vocabulary=omail_duplicate_fields), + default=[u"category", u"folder", u"reply_to", u"dms_files", u"annexes", u"link_to_duplicated"], + ) + # widget("omail_duplicate_display_fields", CheckBoxFieldWidget, multiple="multiple", size=5) + + omail_duplicate_true_default_values = schema.List( + title=_(u"Default values to True when duplicating an outgoing mail"), + description=_(u"If checked, the default value will be True."), + required=False, + value_type=schema.Choice(vocabulary=omail_duplicate_fields), + default=[u"category", u"folder", u"annexes"], + ) + omail_fields = schema.List( title=_(u"${type} fields display", mapping={"type": _("Outgoing mail")}), description=_(u"Configure this carefully. You can order with arrows."), @@ -539,33 +562,6 @@ class IImioDmsMailConfig(model.Schema): default=False, ) - # the duplicate-form default fields - # TODO hide these next two field if not enabled ? - omail_duplicate_default_keep_category = schema.Bool( - title=_(u"Default value when duplicating an outgoing mail for 'Keep classification category'"), - default=True, - ) - omail_duplicate_default_keep_folder = schema.Bool( - title=_(u"Default value when duplicating an outgoing mail for 'Keep classification folder'"), - default=True, - ) - omail_duplicate_default_keep_linked_mails = schema.Bool( - title=_(u"Default value when duplicating an outgoing mail for 'Keep linked mails'"), - default=True, - ) - omail_duplicate_default_keep_dms_files = schema.Bool( - title=_(u"Default value when duplicating an outgoing mail for 'Keep DMS files'"), - default=True, - ) - omail_duplicate_default_keep_annexes = schema.Bool( - title=_(u"Default value when duplicating an outgoing mail for 'Keep annexes'"), - default=True, - ) - omail_duplicate_default_link_to_original = schema.Bool( - title=_(u"Default value when duplicating an outgoing mail for 'Link to original'"), - default=True, - ) - # FIELDSET OEM model.fieldset( "outgoing_email", diff --git a/imio/dms/mail/browser/views.py b/imio/dms/mail/browser/views.py index f39feb91..ae58175d 100644 --- a/imio/dms/mail/browser/views.py +++ b/imio/dms/mail/browser/views.py @@ -10,6 +10,7 @@ from imio.dms.mail import _tr from imio.dms.mail import PMH_ENABLED from imio.dms.mail.browser.settings import IImioDmsMailConfig +from imio.dms.mail.browser.settings import omail_duplicate_fields from imio.dms.mail.browser.table import CKTemplatesTable from imio.dms.mail.browser.table import PersonnelTable from imio.dms.mail.dmsfile import IImioDmsFile @@ -24,6 +25,7 @@ from imio.helpers.fancytree.views import BaseRenderFancyTree from imio.helpers.workflow import do_transitions from imio.helpers.xhtml import object_link +from imio.pyutils.utils import safe_encode from plone import api from plone.supermodel import model from Products.CMFCore.utils import getToolByName @@ -127,9 +129,39 @@ class OMDuplicateForm(Form): """Duplicate an outgoing mail.""" label = _(u"Duplicate mail") - fields = Fields(IOMDuplicateFormSchema) ignoreContext = True + def update(self): + """Handle fields.""" + om_fields = api.portal.get_registry_record("omail_fields", IImioDmsMailConfig, []) + om_fields = [dic["field_name"] for dic in om_fields] + matching = {u"category": "IClassificationFolder.classification_categories", + u"folder": "IClassificationFolder.classification_folders", + u"reply_to": "reply_to", + u"link_to_duplicated": "reply_to"} + to_show = api.portal.get_registry_record("omail_duplicate_display_fields", IImioDmsMailConfig, []) + to_true = api.portal.get_registry_record("omail_duplicate_true_default_values", IImioDmsMailConfig, []) + for term in omail_duplicate_fields: + if ((term.value in matching and matching[term.value] not in om_fields) or + (term.value not in to_show and term.value not in to_true)): + continue + self.fields += Fields( + schema.Bool( + __name__=safe_encode(term.value), + title=term.title, + default=term.value in to_true, + )) + super(OMDuplicateForm, self).update() + + def updateWidgets(self, prefix=None): + super(OMDuplicateForm, self).updateWidgets() + + # we hide fields not to show + to_show = api.portal.get_registry_record("omail_duplicate_display_fields", IImioDmsMailConfig, []) + for term in omail_duplicate_fields: + if term.value not in to_show: + self.widgets[term.value].mode = "hidden" + @button.buttonAndHandler(_('Duplicate'), name='duplicate') def handleApply(self, action): data, errors = self.extractData() @@ -204,24 +236,6 @@ def handleApply(self, action): self.request.response.redirect(duplicated_mail.absolute_url()+"/edit") - def updateWidgets(self): - super(OMDuplicateForm, self).updateWidgets() - - navtree_props = getToolByName(api.portal.get(), 'portal_properties').navtree_properties - excluded_types = navtree_props.getProperty('metaTypesNotToList', ()) - if 'ClassificationContainer' in excluded_types: - self.widgets["keep_category"].mode = "hidden" - else: - self.widgets["keep_category"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_category", IImioDmsMailConfig, True) else [] - if 'ClassificationFolders' in excluded_types: - self.widgets["keep_folder"].mode = "hidden" - else: - self.widgets["keep_folder"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_folder", IImioDmsMailConfig, True) else [] - - self.widgets["keep_linked_mails"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_linked_mails", IImioDmsMailConfig, True) else [] - self.widgets["keep_dms_files"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_dms_files", IImioDmsMailConfig, True) else [] - self.widgets["keep_annexes"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_keep_annexes", IImioDmsMailConfig, True) else [] - self.widgets["link_to_original"].value = ['selected'] if api.portal.get_registry_record("omail_duplicate_default_link_to_original", IImioDmsMailConfig, True) else [] def parse_query(text): """Copied from plone.app.vocabularies.catalog.parse_query but cleaned.""" From fac4f310aac6de0f5ea0fae5bfdaa14d6cfa0a56 Mon Sep 17 00:00:00 2001 From: Stephan Geulette Date: Thu, 7 Aug 2025 12:24:47 +0200 Subject: [PATCH 07/10] Removed useless schema --- imio/dms/mail/browser/views.py | 39 ---------------------------------- 1 file changed, 39 deletions(-) diff --git a/imio/dms/mail/browser/views.py b/imio/dms/mail/browser/views.py index ae58175d..73d45068 100644 --- a/imio/dms/mail/browser/views.py +++ b/imio/dms/mail/browser/views.py @@ -86,45 +86,6 @@ def redirect_url(self, uid): return "{}/persistent-document-generation?{}".format(url, "&".join(params)) -class IOMDuplicateFormSchema(model.Schema): - - keep_category = schema.Bool( - title=_(u"Keep classification category"), - description=u'', - default=True, - ) - - keep_folder = schema.Bool( - title=_(u"Keep classification folder"), - description=u'', - default=True, - ) - - keep_linked_mails = schema.Bool( - title=_(u"Keep linked mails"), - description=u'', - default=True, - ) - - keep_dms_files = schema.Bool( - title=_(u"Keep DMS files"), - description=u'', - default=True, - ) - - keep_annexes = schema.Bool( - title=_(u"Keep annexes"), - description=u'', - default=True, - ) - - link_to_original = schema.Bool( - title=_(u"Link to original"), - description=u'', - default=True, - ) - - class OMDuplicateForm(Form): """Duplicate an outgoing mail.""" From 14e0edb6dfe59d5fd6e885337b61297005b3c0c4 Mon Sep 17 00:00:00 2001 From: Chris Adam Date: Tue, 19 Aug 2025 10:24:07 +0200 Subject: [PATCH 08/10] Fixed duplicated omail ID --- .../templates/actions_panel_duplicate.pt | 2 +- imio/dms/mail/browser/views.py | 65 +++++++------------ 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/imio/dms/mail/browser/templates/actions_panel_duplicate.pt b/imio/dms/mail/browser/templates/actions_panel_duplicate.pt index 338652ad..b78000a8 100644 --- a/imio/dms/mail/browser/templates/actions_panel_duplicate.pt +++ b/imio/dms/mail/browser/templates/actions_panel_duplicate.pt @@ -4,7 +4,7 @@ class="overlay" tal:define="dummy view/saveHasActions;"> - + diff --git a/imio/dms/mail/browser/views.py b/imio/dms/mail/browser/views.py index 73d45068..12fbeda4 100644 --- a/imio/dms/mail/browser/views.py +++ b/imio/dms/mail/browser/views.py @@ -2,9 +2,7 @@ from AccessControl import getSecurityManager from collective.ckeditortemplates.cktemplate import ICKTemplate from collective.dms.basecontent.dmsfile import IDmsAppendixFile -from collective.dms.mailcontent.dmsmail import internalReferenceOutgoingMailDefaultValue from datetime import datetime -from DateTime import DateTime from eea.faceted.vocabularies.autocomplete import IAutocompleteSuggest from imio.dms.mail import _ from imio.dms.mail import _tr @@ -23,12 +21,11 @@ from imio.helpers.emailer import get_mail_host from imio.helpers.emailer import send_email from imio.helpers.fancytree.views import BaseRenderFancyTree +from imio.helpers.transmogrifier import get_correct_id from imio.helpers.workflow import do_transitions from imio.helpers.xhtml import object_link from imio.pyutils.utils import safe_encode from plone import api -from plone.supermodel import model -from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import safe_unicode from Products.Five import BrowserView from Products.PageTemplates.Expressions import SecureModuleImporter @@ -132,49 +129,35 @@ def handleApply(self, action): return # Duplicate the mail - # duplicated_mail.creation_date = DateTime() - # duplicated_mail.reindexObject(idxs=['created']) - # duplicated_mail.internal_reference_no = internalReferenceOutgoingMailDefaultValue(self) - # duplicated_mail.due_date = None - # duplicated_mail.outgoing_date = None - # duplicated_mail.mail_date = None original_mail = self.context - i = 0 - while True: - try: - print(i) - duplicated_mail = sub_create( - api.portal.get()["outgoing-mail"], - "dmsoutgoingmail", - datetime.now(), - "my-id-%s" % i, # TODO use a better id - title=original_mail.title, - description=original_mail.description, - recipients=original_mail.recipients[:] if original_mail.recipients else None, - treating_groups=original_mail.treating_groups[:] if original_mail.treating_groups else None, - assigned_user= original_mail.assigned_user, - sender=original_mail.sender, - recipient_groups=original_mail.recipient_groups[:] if original_mail.recipient_groups else None, - send_modes= original_mail.send_modes[:] if original_mail.send_modes else None, - task_description=original_mail.task_description, - ) - except Exception as e: - i += 1 - if i > 1000: - raise e - else: - break + pc = api.portal.get_tool("portal_catalog") + import ipdb; ipdb.set_trace() + duplicated_mail = sub_create( + api.portal.get()["outgoing-mail"], + "dmsoutgoingmail", + datetime.now(), + get_correct_id([om.getId for om in pc(portal_type="dmsoutgoingmail")], original_mail.getId()), + title=original_mail.title, + description=original_mail.description, + recipients=original_mail.recipients[:] if original_mail.recipients else None, + treating_groups=original_mail.treating_groups[:] if original_mail.treating_groups else None, + assigned_user=original_mail.assigned_user, + sender=original_mail.sender, + recipient_groups=original_mail.recipient_groups[:] if original_mail.recipient_groups else None, + send_modes=original_mail.send_modes[:] if original_mail.send_modes else None, + task_description=original_mail.task_description, + ) - if data['keep_category'] and hasattr(original_mail, 'classification_categories') and original_mail.classification_categories: + if data.get('keep_category', False) and hasattr(original_mail, 'classification_categories') and original_mail.classification_categories: duplicated_mail.classification_categories = original_mail.classification_categories[:] - if data['keep_folder'] and hasattr(original_mail, 'classification_folders') and original_mail.classification_folders: + if data.get('keep_folder', False) and hasattr(original_mail, 'classification_folders') and original_mail.classification_folders: duplicated_mail.classification_folders = original_mail.classification_folders[:] - if data['keep_linked_mails'] and hasattr(original_mail, 'reply_to') and original_mail.reply_to: + if data.get('keep_linked_mails', False) and hasattr(original_mail, 'reply_to') and original_mail.reply_to: duplicated_mail.reply_to = original_mail.reply_to[:] - if data['keep_dms_files']: + if data.get('keep_dms_files', False): # FIXME do not use clipboard dms_files = [sub_content.getId() for sub_content in original_mail.values() if IImioDmsFile.providedBy(sub_content)] import ipdb; ipdb.set_trace() @@ -182,13 +165,13 @@ def handleApply(self, action): clipboard = original_mail.manage_copyObjects(dms_files) duplicated_mail.manage_pasteObjects(clipboard) - if data['keep_annexes']: + if data.get('keep_annexes', False): annexes = [sub_content.getId() for sub_content in original_mail.values() if IDmsAppendixFile.providedBy(sub_content)] if annexes: clipboard = original_mail.manage_copyObjects(annexes) duplicated_mail.manage_pasteObjects(clipboard) - if data['link_to_original']: + if data.get('link_to_original', False): intids = getUtility(IIntIds) rel_id = intids.getId(original_mail) if duplicated_mail.reply_to is None: From 83eabbbb7c536f6c9b04d176b9e04d02980e0bb8 Mon Sep 17 00:00:00 2001 From: Chris Adam Date: Fri, 22 Aug 2025 16:10:58 +0200 Subject: [PATCH 09/10] Omail DMS file duplication --- imio/dms/mail/browser/views.py | 61 ++-------- imio/dms/mail/tests/test_utils.py | 178 ++++++++++++++++++++++++++++++ imio/dms/mail/tests/test_views.py | 174 ----------------------------- imio/dms/mail/utils.py | 84 ++++++++++++++ 4 files changed, 270 insertions(+), 227 deletions(-) diff --git a/imio/dms/mail/browser/views.py b/imio/dms/mail/browser/views.py index 12fbeda4..9720a46c 100644 --- a/imio/dms/mail/browser/views.py +++ b/imio/dms/mail/browser/views.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from AccessControl import getSecurityManager from collective.ckeditortemplates.cktemplate import ICKTemplate -from collective.dms.basecontent.dmsfile import IDmsAppendixFile from datetime import datetime from eea.faceted.vocabularies.autocomplete import IAutocompleteSuggest from imio.dms.mail import _ @@ -13,7 +12,6 @@ from imio.dms.mail.browser.table import PersonnelTable from imio.dms.mail.dmsfile import IImioDmsFile from imio.dms.mail.interfaces import IPersonnelContact -from imio.dms.mail.utils import sub_create from imio.helpers.content import richtextval from imio.helpers.content import uuidToObject from imio.helpers.emailer import add_attachment @@ -21,7 +19,6 @@ from imio.helpers.emailer import get_mail_host from imio.helpers.emailer import send_email from imio.helpers.fancytree.views import BaseRenderFancyTree -from imio.helpers.transmogrifier import get_correct_id from imio.helpers.workflow import do_transitions from imio.helpers.xhtml import object_link from imio.pyutils.utils import safe_encode @@ -33,14 +30,11 @@ from z3c.form import button from z3c.form.field import Fields from z3c.form.form import Form -from z3c.relationfield import RelationValue from zope import schema from zope.annotation import IAnnotations from zope.component import getMultiAdapter -from zope.component import getUtility from zope.i18n import translate from zope.interface import implements -from zope.intid.interfaces import IIntIds from zope.lifecycleevent import modified from zope.pagetemplate.pagetemplate import PageTemplate @@ -129,55 +123,16 @@ def handleApply(self, action): return # Duplicate the mail - original_mail = self.context - pc = api.portal.get_tool("portal_catalog") - import ipdb; ipdb.set_trace() - duplicated_mail = sub_create( - api.portal.get()["outgoing-mail"], - "dmsoutgoingmail", - datetime.now(), - get_correct_id([om.getId for om in pc(portal_type="dmsoutgoingmail")], original_mail.getId()), - title=original_mail.title, - description=original_mail.description, - recipients=original_mail.recipients[:] if original_mail.recipients else None, - treating_groups=original_mail.treating_groups[:] if original_mail.treating_groups else None, - assigned_user=original_mail.assigned_user, - sender=original_mail.sender, - recipient_groups=original_mail.recipient_groups[:] if original_mail.recipient_groups else None, - send_modes=original_mail.send_modes[:] if original_mail.send_modes else None, - task_description=original_mail.task_description, + odm_utils = getMultiAdapter((self.context, self.request), name="odm-utils") + duplicated_mail = odm_utils.duplicate( + keep_category=data.get('category', False), + keep_folder=data.get('folder', False), + keep_reply_to=data.get('reply_to', False), + keep_dms_files=data.get('dms_files', False), + keep_annexes=data.get('annexes', False), + link_to_duplicated=data.get('link_to_duplicated', False), ) - if data.get('keep_category', False) and hasattr(original_mail, 'classification_categories') and original_mail.classification_categories: - duplicated_mail.classification_categories = original_mail.classification_categories[:] - - if data.get('keep_folder', False) and hasattr(original_mail, 'classification_folders') and original_mail.classification_folders: - duplicated_mail.classification_folders = original_mail.classification_folders[:] - - if data.get('keep_linked_mails', False) and hasattr(original_mail, 'reply_to') and original_mail.reply_to: - duplicated_mail.reply_to = original_mail.reply_to[:] - - if data.get('keep_dms_files', False): - # FIXME do not use clipboard - dms_files = [sub_content.getId() for sub_content in original_mail.values() if IImioDmsFile.providedBy(sub_content)] - import ipdb; ipdb.set_trace() - if dms_files: - clipboard = original_mail.manage_copyObjects(dms_files) - duplicated_mail.manage_pasteObjects(clipboard) - - if data.get('keep_annexes', False): - annexes = [sub_content.getId() for sub_content in original_mail.values() if IDmsAppendixFile.providedBy(sub_content)] - if annexes: - clipboard = original_mail.manage_copyObjects(annexes) - duplicated_mail.manage_pasteObjects(clipboard) - - if data.get('link_to_original', False): - intids = getUtility(IIntIds) - rel_id = intids.getId(original_mail) - if duplicated_mail.reply_to is None: - duplicated_mail.reply_to = [] - duplicated_mail.reply_to.append(RelationValue(rel_id)) - self.request.response.redirect(duplicated_mail.absolute_url()+"/edit") diff --git a/imio/dms/mail/tests/test_utils.py b/imio/dms/mail/tests/test_utils.py index 6d8a6aae..4c9c9ee3 100644 --- a/imio/dms/mail/tests/test_utils.py +++ b/imio/dms/mail/tests/test_utils.py @@ -25,6 +25,7 @@ from imio.dms.mail.utils import IdmUtilsMethods from imio.dms.mail.utils import invalidate_users_groups from imio.dms.mail.utils import list_wf_states +from imio.dms.mail.utils import OdmUtilsMethods from imio.dms.mail.utils import PREVIEW_DIR from imio.dms.mail.utils import set_dms_config from imio.dms.mail.utils import sub_create @@ -44,6 +45,7 @@ from Products.CMFPlone.utils import base_hasattr from z3c.relationfield.relation import RelationValue from zope.annotation.interfaces import IAnnotations +from zope.component import getMultiAdapter from zope.component import getUtility from zope.intid.interfaces import IIntIds @@ -759,6 +761,182 @@ def test_IdmUtilsMethods_proposed_to_n_plus_col_cond0(self): self.assertFalse("searchfor_proposed_to_n_plus_1" in im_folder) self.assertTrue("See test_wfadaptations_imservicevalidation.py") + def test_OdmUtilsMethods_duplicate(self): + intids = getUtility(IIntIds) + + omail2 = sub_create( + self.portal["outgoing-mail"], + "dmsoutgoingmail", + datetime.now(), + "my-id2", + title="My title", + description="Description", + send_modes=["post"], + classification_folders=[self.portal.folders['ordre-public-reglement-general-de-police'].UID()], + classification_categories=self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories, + ) + + omail = sub_create( + self.portal["outgoing-mail"], + "dmsoutgoingmail", + datetime.now(), + "my-id", + title="My title", + description="Description", + treating_groups=self.pgof["direction-generale"]["secretariat"].UID(), + send_modes=["post"], + classification_folders=[self.portal.folders['ordre-public-reglement-general-de-police'].UID()], + classification_categories=self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories, + reply_to=[RelationValue(intids.getId(omail2))], + ) + createContentInContainer(omail, "dmsommainfile", title=u"D001", file=NamedBlobFile(filename=u"scanned.pdf")) + createContentInContainer( + omail, "dmsappendixfile", title=u"A001", file=NamedBlobFile(filename=u"appendix.odt") + ) + view = OdmUtilsMethods(omail, omail.REQUEST) + + duplicated_mail = view.duplicate( + keep_category=True, + keep_folder=True, + keep_reply_to=True, + keep_dms_files=True, + keep_annexes=True, + link_to_duplicated=True, + ) + self.assertEqual(duplicated_mail.title, u"My title") + self.assertEqual(duplicated_mail.description, u"Description") + self.assertEqual(duplicated_mail.send_modes, ["post"]) + self.assertGreater(duplicated_mail.creation_date, omail.creation_date) + self.assertNotEqual(duplicated_mail.internal_reference_no, omail.internal_reference_no) + self.assertIsNone(duplicated_mail.mail_date) + self.assertIsNone(duplicated_mail.due_date) + self.assertIsNone(duplicated_mail.outgoing_date) + + # Test keep_category + self.assertEqual(duplicated_mail.classification_categories, self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories) + # Test keep_folder + self.assertEqual(duplicated_mail.classification_folders, [self.portal.folders['ordre-public-reglement-general-de-police'].UID()]) + # Test keep_linked_mails + self.assertEqual(len(duplicated_mail.reply_to), 2) + self.assertEqual(omail2, duplicated_mail.reply_to[0].to_object) + # Test keep_dms_files + self.assertNotIn("d001", duplicated_mail) # By default, we never keep not generated main files + # Test keep_annexes + self.assertIn("a001", duplicated_mail) + # Test link_to_original + self.assertEqual(omail, duplicated_mail.reply_to[1].to_object) + + # Test not keeping categories + duplicated_mail = view.duplicate( + keep_category=False, + keep_folder=True, + keep_reply_to=True, + keep_dms_files=True, + keep_annexes=True, + link_to_duplicated=True, + ) + self.assertIsNone(duplicated_mail.classification_categories) + + # Test not keeping folders + duplicated_mail = view.duplicate( + keep_category=True, + keep_folder=False, + keep_reply_to=True, + keep_dms_files=True, + keep_annexes=True, + link_to_duplicated=True, + ) + self.assertIsNone(duplicated_mail.classification_folders) + + # Test not keeping linked mails + duplicated_mail = view.duplicate( + keep_category=True, + keep_folder=True, + keep_reply_to=False, + keep_dms_files=True, + keep_annexes=True, + link_to_duplicated=True, + ) + self.assertEqual(len(duplicated_mail.reply_to), 1) + self.assertNotEqual(omail2, duplicated_mail.reply_to[0].to_object) + + # Test not keeping dms files + duplicated_mail = view.duplicate( + keep_category=True, + keep_folder=True, + keep_reply_to=True, + keep_dms_files=False, + keep_annexes=True, + link_to_duplicated=True, + ) + self.assertNotIn("d001", duplicated_mail) + + # Test not keeping annexes + duplicated_mail = view.duplicate( + keep_category=True, + keep_folder=True, + keep_reply_to=True, + keep_dms_files=True, + keep_annexes=False, + link_to_duplicated=True, + ) + self.assertNotIn("a001", duplicated_mail) + + # Test not linking to original + duplicated_mail = view.duplicate( + keep_category=True, + keep_folder=True, + keep_reply_to=True, + keep_dms_files=True, + keep_annexes=True, + link_to_duplicated=False, + ) + self.assertEqual(len(duplicated_mail.reply_to), 1) + self.assertNotEqual(omail, duplicated_mail.reply_to[0].to_object) + + # Test with generated main files + generation_view = getMultiAdapter((omail, omail.REQUEST), name="persistent-document-generation") + pod_template = self.portal.templates["om"]["main"] + generation_view.pod_template = pod_template + generation_view.output_format = 'odt' + generation_view.generate_persistent_doc(pod_template, 'odt') + duplicated_mail = view.duplicate( + keep_category=True, + keep_folder=True, + keep_reply_to=True, + keep_dms_files=True, + keep_annexes=True, + link_to_duplicated=True, + ) + self.assertIn('012999900000002', duplicated_mail) + dms_file = duplicated_mail['012999900000002'] + annot = IAnnotations(dms_file)['documentgenerator'] + self.assertEqual(annot['template_uid'], pod_template.UID()) + + # Test with mailings (publipostage) + omail.recipients = [RelationValue(intids.getId(self.contacts["jeancourant"]["agent-electrabel"])), RelationValue(intids.getId(self.contacts["sergerobinet"]["agent-swde"]))] + generation_view.generate_persistent_doc(pod_template, 'odt') + mailing_loop_generation_view = getMultiAdapter((omail, omail.REQUEST), name="mailing-loop-persistent-document-generation") + mailing_loop_pod_template = self.portal.templates["om"]["mailing"] + mailing_loop_generation_view.orig_template = pod_template + mailing_loop_generation_view.pod_template = mailing_loop_pod_template + mailing_loop_generation_view.output_format = 'odt' + mailing_loop_generation_view.document = omail['012999900000001'] + mailing_loop_generation_view.generate_persistent_doc(mailing_loop_pod_template, 'odt') + duplicated_mail = view.duplicate( + keep_category=True, + keep_folder=True, + keep_reply_to=True, + keep_dms_files=True, + keep_annexes=True, + link_to_duplicated=True, + ) + self.assertEqual(duplicated_mail.keys(), ['012999900000004', 'a001']) + dms_file = duplicated_mail['012999900000004'] + annot = IAnnotations(dms_file)['documentgenerator'] + self.assertEqual(annot['template_uid'], pod_template.UID()) + self.assertTrue(annot['need_mailing']) + def test_create_period_folder(self): dte = datetime.now() - timedelta(days=7) foldername = dte.strftime("%Y%U") # week diff --git a/imio/dms/mail/tests/test_views.py b/imio/dms/mail/tests/test_views.py index 0d0062c9..52527ea6 100644 --- a/imio/dms/mail/tests/test_views.py +++ b/imio/dms/mail/tests/test_views.py @@ -305,177 +305,3 @@ def test_call(self): self.assertIn(u">012/34.56.79<", rendered) self.assertIn(u">Rue Léon Morel, 1<", rendered) self.assertIn(u">5032 Isnes<", rendered) - -class TestDuplicate(unittest.TestCase, ImioTestHelpers): - """ - Test the duplication of outgoing mails. - """ - - layer = DMSMAIL_INTEGRATION_TESTING - - def setUp(self): - self.portal = self.layer["portal"] - self.request = self.portal.REQUEST - self.pc = api.portal.get_tool('portal_catalog') - self.change_user("siteadmin") - self.intids = getUtility(IIntIds) - - self.omail2 = sub_create( - self.portal["outgoing-mail"], - "dmsoutgoingmail", - datetime.now(), - "my-id2", - title="My title", - description="Description", - send_modes=["post"], - classification_folders=[self.portal.folders['ordre-public-reglement-general-de-police'].UID()], - classification_categories=self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories, - ) - - self.omail = sub_create( - self.portal["outgoing-mail"], - "dmsoutgoingmail", - datetime.now(), - "my-id", - title="My title", - description="Description", - send_modes=["post"], - classification_folders=[self.portal.folders['ordre-public-reglement-general-de-police'].UID()], - classification_categories=self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories, - reply_to=[RelationValue(self.intids.getId(self.omail2))], - ) - self.dmsommainfile = createContentInContainer(self.omail, "dmsommainfile", title=u"D001", file=NamedBlobFile(filename=u"scanned.pdf")) - self.dmsappendixfile = createContentInContainer( - self.omail, "dmsappendixfile", title=u"A001", file=NamedBlobFile(filename=u"appendix.odt") - ) - self.form = self.omail.restrictedTraverse("@@duplicate") - - @patch("imio.dms.mail.browser.views.DuplicateForm.extractData") - def test_duplicate(self, extractData): - extractData.return_value = { - 'keep_category': True, - 'keep_folder': True, - 'keep_linked_mails': True, - 'keep_dms_files': True, - 'keep_annexes': True, - 'link_to_original': True, - }, None - self.form.handleApply(self.form, "duplicate") - - brains = self.pc(portal_type="dmsoutgoingmail", id="copy_of_my-id") - self.assertEqual(len(brains), 1) - duplicated_mail = brains[0].getObject() - self.assertEqual(duplicated_mail.title, u"My title") - self.assertEqual(duplicated_mail.description, u"Description") - self.assertEqual(duplicated_mail.send_modes, ["post"]) - self.assertGreater(duplicated_mail.creation_date, self.omail.creation_date) - self.assertNotEqual(duplicated_mail.internal_reference_no, self.omail.internal_reference_no) - self.assertIsNone(duplicated_mail.mail_date) - self.assertIsNone(duplicated_mail.due_date) - self.assertIsNone(duplicated_mail.outgoing_date) - - # Test keep_category - self.assertEqual(duplicated_mail.classification_categories, self.portal.folders['ordre-public-reglement-general-de-police'].classification_categories) - # Test keep_folder - self.assertEqual(duplicated_mail.classification_folders, [self.portal.folders['ordre-public-reglement-general-de-police'].UID()]) - # Test keep_linked_mails - self.assertEqual(len(duplicated_mail.reply_to), 2) - self.assertEqual(self.omail2, duplicated_mail.reply_to[0].to_object) - # Test keep_dms_files - self.assertIn("d001", duplicated_mail) - # Test keep_annexes - self.assertIn("a001", duplicated_mail) - # Test link_to_original - self.assertEqual(self.omail, duplicated_mail.reply_to[1].to_object) - - # Test not keeping categories - extractData.return_value = { - 'keep_category': False, - 'keep_folder': True, - 'keep_linked_mails': True, - 'keep_dms_files': True, - 'keep_annexes': True, - 'link_to_original': True, - }, None - self.form.handleApply(self.form, "duplicate") - brains = self.pc(portal_type="dmsoutgoingmail", id="copy2_of_my-id") - self.assertEqual(len(brains), 1) - duplicated_mail = brains[0].getObject() - self.assertIsNone(duplicated_mail.classification_categories) - - # Test not keeping folders - extractData.return_value = { - 'keep_category': True, - 'keep_folder': False, - 'keep_linked_mails': True, - 'keep_dms_files': True, - 'keep_annexes': True, - 'link_to_original': True, - }, None - self.form.handleApply(self.form, "duplicate") - brains = self.pc(portal_type="dmsoutgoingmail", id="copy3_of_my-id") - self.assertEqual(len(brains), 1) - duplicated_mail = brains[0].getObject() - self.assertIsNone(duplicated_mail.classification_folders) - - # Test not keeping linked mails - extractData.return_value = { - 'keep_category': True, - 'keep_folder': True, - 'keep_linked_mails': False, - 'keep_dms_files': True, - 'keep_annexes': True, - 'link_to_original': True, - }, None - self.form.handleApply(self.form, "duplicate") - brains = self.pc(portal_type="dmsoutgoingmail", id="copy4_of_my-id") - self.assertEqual(len(brains), 1) - duplicated_mail = brains[0].getObject() - self.assertEqual(len(duplicated_mail.reply_to), 1) - self.assertNotEqual(self.omail2, duplicated_mail.reply_to[0].to_object) - - # Test not keeping dms files - extractData.return_value = { - 'keep_category': True, - 'keep_folder': True, - 'keep_linked_mails': True, - 'keep_dms_files': False, - 'keep_annexes': True, - 'link_to_original': True, - }, None - self.form.handleApply(self.form, "duplicate") - brains = self.pc(portal_type="dmsoutgoingmail", id="copy5_of_my-id") - self.assertEqual(len(brains), 1) - duplicated_mail = brains[0].getObject() - self.assertNotIn("d001", duplicated_mail) - - # Test not keeping annexes - extractData.return_value = { - 'keep_category': True, - 'keep_folder': True, - 'keep_linked_mails': True, - 'keep_dms_files': True, - 'keep_annexes': False, - 'link_to_original': True, - }, None - self.form.handleApply(self.form, "duplicate") - brains = self.pc(portal_type="dmsoutgoingmail", id="copy6_of_my-id") - self.assertEqual(len(brains), 1) - duplicated_mail = brains[0].getObject() - self.assertNotIn("a001", duplicated_mail) - - # Test not linking to original - extractData.return_value = { - 'keep_category': True, - 'keep_folder': True, - 'keep_linked_mails': True, - 'keep_dms_files': True, - 'keep_annexes': True, - 'link_to_original': False, - }, None - self.form.handleApply(self.form, "duplicate") - brains = self.pc(portal_type="dmsoutgoingmail", id="copy7_of_my-id") - self.assertEqual(len(brains), 1) - duplicated_mail = brains[0].getObject() - self.assertEqual(len(duplicated_mail.reply_to), 1) - self.assertNotEqual(self.omail, duplicated_mail.reply_to[0].to_object) diff --git a/imio/dms/mail/utils.py b/imio/dms/mail/utils.py index 38b6f1b6..1aca6be1 100644 --- a/imio/dms/mail/utils.py +++ b/imio/dms/mail/utils.py @@ -3,6 +3,8 @@ from collective.behavior.talcondition.utils import _evaluateExpression from collective.contact.plonegroup.config import get_registry_organizations from collective.contact.plonegroup.utils import organizations_with_suffixes +from collective.dms.basecontent.dmsfile import IDmsAppendixFile +from collective.documentgenerator.content.pod_template import IMailingLoopTemplate from collective.documentviewer.convert import Converter from collective.documentviewer.convert import saveFileToBlob from collective.eeafaceted.collectionwidget.utils import _updateDefaultCollectionFor @@ -20,6 +22,7 @@ from imio.dms.mail import MAIN_FOLDERS from imio.dms.mail import PERIODS from imio.dms.mail import PRODUCT_DIR +from imio.dms.mail.dmsfile import IImioDmsFile from imio.dms.mail.interfaces import IProtectedItem from imio.helpers.batching import batch_delete_files from imio.helpers.batching import batch_get_keys @@ -36,6 +39,7 @@ from imio.helpers.content import object_values from imio.helpers.content import uuidToObject from imio.helpers.security import check_zope_admin +from imio.helpers.transmogrifier import get_correct_id from imio.helpers.workflow import do_transitions from imio.helpers.xhtml import object_link from interfaces import IIMDashboard @@ -63,6 +67,7 @@ from zc.relation.interfaces import ICatalog from zExceptions import Redirect from zope.annotation.interfaces import IAnnotations +from zope.component import getMultiAdapter from zope.component import getUtility from zope.component.hooks import getSite from zope.interface import alsoProvides @@ -1184,6 +1189,85 @@ def scanned_col_cond(self): """Condition for searchfor_scanned collection""" return self.is_in_user_groups(["encodeurs", "expedition"], admin=False, suffixes=[CREATING_GROUP_SUFFIX]) + def duplicate(self, keep_category, keep_folder, keep_reply_to, keep_dms_files, keep_annexes, link_to_duplicated): + original_mail = self.context + pc = api.portal.get_tool("portal_catalog") + duplicated_mail = sub_create( + api.portal.get()["outgoing-mail"], + "dmsoutgoingmail", + datetime.now(), + get_correct_id([om.getId for om in pc(portal_type="dmsoutgoingmail")], original_mail.getId()), + title=original_mail.title, + description=original_mail.description, + recipients=original_mail.recipients[:] if original_mail.recipients else None, + treating_groups=original_mail.treating_groups[:] if original_mail.treating_groups else None, + assigned_user=original_mail.assigned_user, + sender=original_mail.sender, + recipient_groups=original_mail.recipient_groups[:] if original_mail.recipient_groups else None, + send_modes=original_mail.send_modes[:] if original_mail.send_modes else None, + task_description=original_mail.task_description, + ) + + if keep_category and hasattr(original_mail, 'classification_categories') and original_mail.classification_categories: + duplicated_mail.classification_categories = original_mail.classification_categories[:] + else: + duplicated_mail.classification_categories = None + + if keep_folder and hasattr(original_mail, 'classification_folders') and original_mail.classification_folders: + duplicated_mail.classification_folders = original_mail.classification_folders[:] + else: + duplicated_mail.classification_folders = None + + if keep_reply_to and hasattr(original_mail, 'reply_to') and original_mail.reply_to: + duplicated_mail.reply_to = original_mail.reply_to[:] + + if keep_dms_files: + dms_files = [sub_content for sub_content in original_mail.values() if sub_content.portal_type == self.mainfile_type] + used_template_uids = set() + for dms_file in dms_files: + annot = IAnnotations(dms_file).get('documentgenerator', {}) + + # Skip if document was not generated (could be scanned) + if not annot: + continue + + # Skip if document was already generated from the same template + if annot.get('template_uid') in used_template_uids: + continue + + # Skip if document is final step of a mailing + document_generation_helper_view = getMultiAdapter((duplicated_mail, self.request), name="document_generation_helper_view") + requires_mailing = len(document_generation_helper_view.mailing_list()) > 1 + if requires_mailing and not annot.get('need_mailing'): + continue + + # Generate a new document from the same template + template_uid = annot.get('template_uid') + generation_view = getMultiAdapter((duplicated_mail, self.request), name="persistent-document-generation") + pod_template = generation_view.get_pod_template(template_uid) + # Skip if it's a mailing loop template + if IMailingLoopTemplate.providedBy(pod_template): + continue + generation_view.pod_template = pod_template + generation_view.output_format = 'odt' + generation_view.generate_persistent_doc(pod_template, 'odt') + used_template_uids.add(template_uid) + + if keep_annexes: + annexes = [sub_content.getId() for sub_content in original_mail.values() if IDmsAppendixFile.providedBy(sub_content)] + if annexes: + clipboard = original_mail.manage_copyObjects(annexes) + duplicated_mail.manage_pasteObjects(clipboard) + + if link_to_duplicated: + intids = getUtility(IIntIds) + rel_id = intids.getId(original_mail) + if duplicated_mail.reply_to is None: + duplicated_mail.reply_to = [] + duplicated_mail.reply_to.append(RelationValue(rel_id)) + + return duplicated_mail + class Dummy(object): """dummy class that allows setting attributes""" From 2cec978c80313f26727cc2a85fddfe8d7f08a976 Mon Sep 17 00:00:00 2001 From: Chris Adam Date: Fri, 22 Aug 2025 16:39:31 +0200 Subject: [PATCH 10/10] Moved copy dms files after duplication to execute after next edit --- imio/dms/mail/subscribers.py | 11 +++++ imio/dms/mail/subscribers.zcml | 6 +++ imio/dms/mail/tests/test_utils.py | 2 +- imio/dms/mail/utils.py | 67 +++++++++++++++++-------------- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/imio/dms/mail/subscribers.py b/imio/dms/mail/subscribers.py index 2390c21f..d7eca249 100644 --- a/imio/dms/mail/subscribers.py +++ b/imio/dms/mail/subscribers.py @@ -72,6 +72,7 @@ # from zope.component.interfaces import ComponentLookupError from zope.annotation import IAnnotations from zope.component import getAdapter +from zope.component import getMultiAdapter from zope.component import getSiteManager from zope.component import getUtility from zope.component import queryUtility @@ -384,6 +385,16 @@ def dmsoutgoingmail_transition(mail, event): mail.portal_catalog.reindexObject(mail, idxs=("in_out_date",), update_metadata=0) +def dmsoutgoingmail_modified(mail, event): + annot = IAnnotations(mail).get('imio.dms.mail', {}) + copy_dms_files_from = annot.get('copy_dms_files_from') + if copy_dms_files_from: + del annot['copy_dms_files_from'] + original_mail = uuidToObject(copy_dms_files_from, unrestricted=True) + odm_utils = getMultiAdapter((mail, mail.REQUEST), name="odm-utils") + odm_utils.copy_dms_files(original_mail) + + def dv_handle_file_creation(obj, event): """Intermediate function to avoid converting some files in documentviewer""" if obj.portal_type in DV_AVOIDED_TYPES: diff --git a/imio/dms/mail/subscribers.zcml b/imio/dms/mail/subscribers.zcml index 7e357c39..bde5b4cb 100644 --- a/imio/dms/mail/subscribers.zcml +++ b/imio/dms/mail/subscribers.zcml @@ -98,6 +98,12 @@ handler=".subscribers.dmsoutgoingmail_transition" /> + + 1 - if requires_mailing and not annot.get('need_mailing'): - continue - - # Generate a new document from the same template - template_uid = annot.get('template_uid') - generation_view = getMultiAdapter((duplicated_mail, self.request), name="persistent-document-generation") - pod_template = generation_view.get_pod_template(template_uid) - # Skip if it's a mailing loop template - if IMailingLoopTemplate.providedBy(pod_template): - continue - generation_view.pod_template = pod_template - generation_view.output_format = 'odt' - generation_view.generate_persistent_doc(pod_template, 'odt') - used_template_uids.add(template_uid) + # Add an annotation to copy DMS files only after next edit + annot = IAnnotations(duplicated_mail) + annot.setdefault('imio.dms.mail', PersistentDict()) # make sure the key exists + annot['imio.dms.mail']['copy_dms_files_from'] = original_mail.UID() if keep_annexes: annexes = [sub_content.getId() for sub_content in original_mail.values() if IDmsAppendixFile.providedBy(sub_content)] @@ -1268,6 +1242,39 @@ def duplicate(self, keep_category, keep_folder, keep_reply_to, keep_dms_files, k return duplicated_mail + def copy_dms_files(self, original_mail): + """Re-generate DMS files on this mail based on the templates used in the original mail.""" + dms_files = [sub_content for sub_content in original_mail.values() if sub_content.portal_type == self.mainfile_type] + used_template_uids = set() + for dms_file in dms_files: + annot = IAnnotations(dms_file).get('documentgenerator', {}) + + # Skip if document was not generated (could be scanned) + if not annot: + continue + + # Skip if document was already generated from the same template + if annot.get('template_uid') in used_template_uids: + continue + + # Skip if document is final step of a mailing + document_generation_helper_view = getMultiAdapter((self.context, self.request), name="document_generation_helper_view") + requires_mailing = len(document_generation_helper_view.mailing_list()) > 1 + if requires_mailing and not annot.get('need_mailing'): + continue + + # Generate a new document from the same template + template_uid = annot.get('template_uid') + generation_view = getMultiAdapter((self.context, self.request), name="persistent-document-generation") + pod_template = generation_view.get_pod_template(template_uid) + # Skip if it's a mailing loop template + if IMailingLoopTemplate.providedBy(pod_template): + continue + generation_view.pod_template = pod_template + generation_view.output_format = 'odt' + generation_view.generate_persistent_doc(pod_template, 'odt') + used_template_uids.add(template_uid) + class Dummy(object): """dummy class that allows setting attributes"""