From 5d5e9677561a3e28cc24b2a63d36dc1ec3be4ce7 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Tue, 26 May 2026 19:56:13 -0400 Subject: [PATCH 1/8] [MIG] dms_field: Migration to 19.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrote setUpClass to create fixtures inline (storage, dms.access.group, dms.field.template, template root directory) — OCA CI runs without demo data so env.ref("dms_field.field_template_partner") would fail --- dms_field/tests/test_dms_field.py | 34 ++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/dms_field/tests/test_dms_field.py b/dms_field/tests/test_dms_field.py index 1d4cc956d..6031f3248 100644 --- a/dms_field/tests/test_dms_field.py +++ b/dms_field/tests/test_dms_field.py @@ -20,11 +20,35 @@ def setUpClass(cls): {"name": "Test group", "users": [(4, cls.user_a.id)]} ) cls.user_b = new_test_user(cls.env, login="test-user-b") - cls.template = cls.env.ref("dms_field.field_template_partner") - cls.template.group_ids.group_ids = [(4, cls.group.id)] - cls.template.group_ids.explicit_user_ids = [(4, cls.user_b.id)] - cls.storage = cls.template.storage_id - cls.directory = cls.template.dms_directory_ids + # Create fixtures directly — OCA CI runs without demo data. + cls.storage = cls.env["dms.storage"].create( + {"name": "Test Storage", "save_type": "database"} + ) + cls.access_group = cls.env["dms.access.group"].create( + { + "name": "Test DMS Access Group", + "perm_create": True, + "perm_write": True, + "perm_unlink": True, + "group_ids": [(4, cls.group.id)], + "explicit_user_ids": [(4, cls.user_b.id)], + } + ) + cls.template = cls.env["dms.field.template"].create( + { + "name": "Partner", + "storage_id": cls.storage.id, + "model_id": cls.env.ref("base.model_res_partner").id, + "group_ids": [(4, cls.access_group.id)], + } + ) + # Manually create the template's own root directory + # (normally auto-created via install_mode in production). + cls.directory = ( + cls.env["dms.field.template"] + .with_context(res_model="dms.field.template", res_id=cls.template.id) + .create_dms_directory() + ) cls.subdirectory_1 = cls.env["dms.directory"].create( { "name": "Test subdirectory 1", From 87e079fb2077600a46e9e3017e06ce274bee35db Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Tue, 26 May 2026 19:57:06 -0400 Subject: [PATCH 2/8] [IMP] dms_field: UX modernisation (form hero slot, FilePreviewPane in dms_list) - Add hero_chips named slot to dms.field.template form view - Replace custom file-node preview in dms_list renderer with FilePreviewPane from base DMS (download, open-form, share, extra-action registry) - Directory-node panel retained; file branch now leverages shared preview infrastructure --- .../views/dms_list/dms_list_renderer.esm.js | 2 + .../src/views/dms_list/dms_list_renderer.xml | 123 +++++++----------- dms_field/views/dms_field_template_views.xml | 1 + 3 files changed, 51 insertions(+), 75 deletions(-) diff --git a/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js b/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js index 9ff909d93..240d31b26 100644 --- a/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js +++ b/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js @@ -14,6 +14,7 @@ import {_t} from "@web/core/l10n/translation"; import {download} from "@web/core/network/download"; import {useFileViewer} from "@web/core/file_viewer/file_viewer_hook"; import {useService} from "@web/core/utils/hooks"; +import {FilePreviewPane} from "@dms/js/components/preview/file_preview_pane.esm"; export class DmsListRenderer extends Component { setup() { @@ -549,3 +550,4 @@ export class DmsListRenderer extends Component { } DmsListRenderer.template = "dms_list.Renderer"; +DmsListRenderer.components = {FilePreviewPane}; diff --git a/dms_field/static/src/views/dms_list/dms_list_renderer.xml b/dms_field/static/src/views/dms_list/dms_list_renderer.xml index 061593afc..0a42de65d 100644 --- a/dms_field/static/src/views/dms_list/dms_list_renderer.xml +++ b/dms_field/static/src/views/dms_list/dms_list_renderer.xml @@ -37,87 +37,60 @@ -
-
-
-
-
- - + + + + + +
+
+ +
+

+ +

+

+ Subdirectories: + +

+

+ Files: + +

+

+ Size: + +

+

+ Elements: + +

-
-

- -

- -

- Subdirectories: - -

-

- Files: - -

-

- Size: - -

-

- Elements: - -

-
- -

- Size: - -

-
-
- - - Download - - -
-
-
-
+ + diff --git a/dms_field/views/dms_field_template_views.xml b/dms_field/views/dms_field_template_views.xml index d0d6a0399..31c23eb55 100644 --- a/dms_field/views/dms_field_template_views.xml +++ b/dms_field/views/dms_field_template_views.xml @@ -22,6 +22,7 @@

+
From 179953cad7f1f74b56949873b0f26cc0d995c015 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Tue, 26 May 2026 21:56:44 -0400 Subject: [PATCH 3/8] [ADD] dms_field_auto_classification: replay 18.0 module content (Tecnativa) --- dms_field_auto_classification/README.rst | 124 +++++ dms_field_auto_classification/__init__.py | 4 + dms_field_auto_classification/__manifest__.py | 19 + .../demo/dms_classification_template_demo.xml | 20 + dms_field_auto_classification/i18n/ar.po | 107 ++++ .../i18n/dms_field_auto_classification.pot | 105 ++++ dms_field_auto_classification/i18n/es.po | 114 +++++ dms_field_auto_classification/i18n/it.po | 113 +++++ dms_field_auto_classification/i18n/sv.po | 106 ++++ .../models/__init__.py | 3 + .../models/dms_classification_template.py | 46 ++ dms_field_auto_classification/pyproject.toml | 3 + .../readme/CONFIGURE.md | 14 + .../readme/CONTRIBUTORS.md | 3 + .../readme/DESCRIPTION.md | 2 + dms_field_auto_classification/readme/USAGE.md | 10 + .../security/ir.model.access.csv | 2 + .../static/description/icon.png | Bin 0 -> 5069 bytes .../static/description/icon.svg | 210 ++++++++ .../static/description/index.html | 470 ++++++++++++++++++ .../tests/__init__.py | 3 + .../tests/data/test.zip | Bin 0 -> 2902 bytes .../test_dms_field_auto_classification.py | 193 +++++++ .../dms_classification_template_views.xml | 37 ++ .../wizards/__init__.py | 3 + .../wizards/wizard_dms_classification.py | 83 ++++ .../wizard_dms_classification_views.xml | 19 + 27 files changed, 1813 insertions(+) create mode 100644 dms_field_auto_classification/README.rst create mode 100644 dms_field_auto_classification/__init__.py create mode 100644 dms_field_auto_classification/__manifest__.py create mode 100644 dms_field_auto_classification/demo/dms_classification_template_demo.xml create mode 100644 dms_field_auto_classification/i18n/ar.po create mode 100644 dms_field_auto_classification/i18n/dms_field_auto_classification.pot create mode 100644 dms_field_auto_classification/i18n/es.po create mode 100644 dms_field_auto_classification/i18n/it.po create mode 100644 dms_field_auto_classification/i18n/sv.po create mode 100644 dms_field_auto_classification/models/__init__.py create mode 100644 dms_field_auto_classification/models/dms_classification_template.py create mode 100644 dms_field_auto_classification/pyproject.toml create mode 100644 dms_field_auto_classification/readme/CONFIGURE.md create mode 100644 dms_field_auto_classification/readme/CONTRIBUTORS.md create mode 100644 dms_field_auto_classification/readme/DESCRIPTION.md create mode 100644 dms_field_auto_classification/readme/USAGE.md create mode 100644 dms_field_auto_classification/security/ir.model.access.csv create mode 100644 dms_field_auto_classification/static/description/icon.png create mode 100644 dms_field_auto_classification/static/description/icon.svg create mode 100644 dms_field_auto_classification/static/description/index.html create mode 100644 dms_field_auto_classification/tests/__init__.py create mode 100644 dms_field_auto_classification/tests/data/test.zip create mode 100644 dms_field_auto_classification/tests/test_dms_field_auto_classification.py create mode 100644 dms_field_auto_classification/views/dms_classification_template_views.xml create mode 100644 dms_field_auto_classification/wizards/__init__.py create mode 100644 dms_field_auto_classification/wizards/wizard_dms_classification.py create mode 100644 dms_field_auto_classification/wizards/wizard_dms_classification_views.xml diff --git a/dms_field_auto_classification/README.rst b/dms_field_auto_classification/README.rst new file mode 100644 index 000000000..88012d0b8 --- /dev/null +++ b/dms_field_auto_classification/README.rst @@ -0,0 +1,124 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +===================================== +Auto classify files into embedded DMS +===================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:08d9cc488429baf098763c68704aa8516ffc5b2f1ff40c2b7422db0be35d39ff + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fdms-lightgray.png?logo=github + :target: https://github.com/OCA/dms/tree/18.0/dms_field_auto_classification + :alt: OCA/dms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/dms-18-0/dms-18-0-dms_field_auto_classification + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/dms&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Automatically classify files within a .zip file to the corresponding +directory(s) related to an embedded DMS. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +1. Go to Documents / Configuration / Classification Templates and create + or edit a template. +2. You can set a model to which it is linked (res.partner for example). +3. You can define the details to indicate which field is referenced by + the defined filename pattern. + +Full example from res.partner: + +Filename pattern: ([0-9]{8}[A-Z]).\*.pdf Details: VAT (field) and 0 +(index) Directory Pattern example 1: {0} > This will attempt to add the +files to the directory linked to the partner with the VAT name. +Directory Pattern example 2: {0} / Misc > This will attempt to add the +files to the "Misc" subdirectory linked to the partner with the VAT +name. + +Usage +===== + +1. Go to Documents / Auto Classification and select a template and a + .zip file. +2. Press the Analyze button +3. As many lines will be set as the number of files contained in the + .zip file and apply the filename pattern. +4. The record to which they are related (res.partner for example) will + be show on the lines. +5. Press the Classify button +6. The files (dms.file) will be created in the corresponding + directories. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__: + + - Víctor Martínez + - Pedro M. Baeza + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-victoralmau| image:: https://github.com/victoralmau.png?size=40px + :target: https://github.com/victoralmau + :alt: victoralmau + +Current `maintainer `__: + +|maintainer-victoralmau| + +This module is part of the `OCA/dms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/dms_field_auto_classification/__init__.py b/dms_field_auto_classification/__init__.py new file mode 100644 index 000000000..e1e144406 --- /dev/null +++ b/dms_field_auto_classification/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import models +from . import wizards diff --git a/dms_field_auto_classification/__manifest__.py b/dms_field_auto_classification/__manifest__.py new file mode 100644 index 000000000..f4e7154f8 --- /dev/null +++ b/dms_field_auto_classification/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Auto classify files into embedded DMS", + "version": "18.0.1.0.1", + "category": "Document Management", + "website": "https://github.com/OCA/dms", + "author": "Tecnativa, Odoo Community Association (OCA)", + "license": "AGPL-3", + "depends": ["dms_auto_classification", "dms_field"], + "installable": True, + "data": [ + "security/ir.model.access.csv", + "views/dms_classification_template_views.xml", + "wizards/wizard_dms_classification_views.xml", + ], + "demo": ["demo/dms_classification_template_demo.xml"], + "maintainers": ["victoralmau"], +} diff --git a/dms_field_auto_classification/demo/dms_classification_template_demo.xml b/dms_field_auto_classification/demo/dms_classification_template_demo.xml new file mode 100644 index 000000000..261edd865 --- /dev/null +++ b/dms_field_auto_classification/demo/dms_classification_template_demo.xml @@ -0,0 +1,20 @@ + + + + Partners template + ([0-9]{8}[A-Z]).*.pdf + + {0} + + + + + 0 + + diff --git a/dms_field_auto_classification/i18n/ar.po b/dms_field_auto_classification/i18n/ar.po new file mode 100644 index 000000000..b1c7b59df --- /dev/null +++ b/dms_field_auto_classification/i18n/ar.po @@ -0,0 +1,107 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * dms_field_auto_classification +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_uid +msgid "Created by" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_date +msgid "Created on" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__detail_ids +msgid "Details" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__display_name +msgid "Display Name" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template +msgid "Dms Classification Template" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template_detail +msgid "Dms Classification Template Detail" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__field_id +msgid "Field" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__id +msgid "ID" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__index +msgid "Index" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_date +msgid "Last Updated on" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model_id +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__model_id +msgid "Model" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model +msgid "Model name" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__parent_id +msgid "Parent" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__record_ref +msgid "Record Referenced" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__sequence +msgid "Sequence" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__template_id +msgid "Template" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_wizard_dms_classification_detail +msgid "Wizard Dms Classification Detail" +msgstr "" diff --git a/dms_field_auto_classification/i18n/dms_field_auto_classification.pot b/dms_field_auto_classification/i18n/dms_field_auto_classification.pot new file mode 100644 index 000000000..8e34de915 --- /dev/null +++ b/dms_field_auto_classification/i18n/dms_field_auto_classification.pot @@ -0,0 +1,105 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * dms_field_auto_classification +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_uid +msgid "Created by" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_date +msgid "Created on" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__detail_ids +msgid "Details" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__display_name +msgid "Display Name" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template +msgid "Dms Classification Template" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template_detail +msgid "Dms Classification Template Detail" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__field_id +msgid "Field" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__id +msgid "ID" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__index +msgid "Index" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_date +msgid "Last Updated on" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model_id +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__model_id +msgid "Model" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model +msgid "Model name" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__parent_id +msgid "Parent" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__record_ref +msgid "Record Referenced" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__sequence +msgid "Sequence" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__template_id +msgid "Template" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_wizard_dms_classification_detail +msgid "Wizard Dms Classification Detail" +msgstr "" diff --git a/dms_field_auto_classification/i18n/es.po b/dms_field_auto_classification/i18n/es.po new file mode 100644 index 000000000..b2c2b1094 --- /dev/null +++ b/dms_field_auto_classification/i18n/es.po @@ -0,0 +1,114 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * dms_field_auto_classification +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-01-23 12:22+0000\n" +"PO-Revision-Date: 2026-02-19 14:40+0000\n" +"Last-Translator: herminioelx \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.15.2\n" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__detail_ids +msgid "Details" +msgstr "Detalles" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__display_name +msgid "Display Name" +msgstr "Nombre a Mostrar" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template +msgid "Dms Classification Template" +msgstr "Plantilla de clasificación Dms" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template_detail +msgid "Dms Classification Template Detail" +msgstr "Plantilla de Clasificación Dms Detalle" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__field_id +msgid "Field" +msgstr "Campo" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__id +msgid "ID" +msgstr "ID" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__index +msgid "Index" +msgstr "Índice" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail____last_update +msgid "Last Modified on" +msgstr "Última Modificación el" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_uid +msgid "Last Updated by" +msgstr "Actualizado por última vez por" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_date +msgid "Last Updated on" +msgstr "Última modificación el" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model_id +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__model_id +msgid "Model" +msgstr "Modelo" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model +msgid "Model name" +msgstr "Nombre del modelo" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__parent_id +msgid "Parent" +msgstr "Padre" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__record_ref +msgid "Record Referenced" +msgstr "Registro referenciado" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__template_id +msgid "Template" +msgstr "Plantilla" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_wizard_dms_classification_detail +msgid "Wizard Dms Classification Detail" +msgstr "Asistente de Clasificación Dms Detalle" diff --git a/dms_field_auto_classification/i18n/it.po b/dms_field_auto_classification/i18n/it.po new file mode 100644 index 000000000..64ca3fdf4 --- /dev/null +++ b/dms_field_auto_classification/i18n/it.po @@ -0,0 +1,113 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * dms_field_auto_classification +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-10-03 10:06+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.6.2\n" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__detail_ids +msgid "Details" +msgstr "Dettagli" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template +msgid "Dms Classification Template" +msgstr "Modello classificazione DMS" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template_detail +msgid "Dms Classification Template Detail" +msgstr "Dettagli modello classificazione DMS" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__field_id +msgid "Field" +msgstr "Campo" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__id +msgid "ID" +msgstr "ID" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__index +msgid "Index" +msgstr "Indice" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model_id +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__model_id +msgid "Model" +msgstr "Modello" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model +msgid "Model name" +msgstr "Nome modello" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__parent_id +msgid "Parent" +msgstr "Padre" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__record_ref +msgid "Record Referenced" +msgstr "Record referenziato" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__sequence +msgid "Sequence" +msgstr "Sequenza" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__template_id +msgid "Template" +msgstr "Modello" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_wizard_dms_classification_detail +msgid "Wizard Dms Classification Detail" +msgstr "Dettagli procedura guidata classificazione DMS" diff --git a/dms_field_auto_classification/i18n/sv.po b/dms_field_auto_classification/i18n/sv.po new file mode 100644 index 000000000..2a5849f8d --- /dev/null +++ b/dms_field_auto_classification/i18n/sv.po @@ -0,0 +1,106 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * dms_field_auto_classification +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_uid +msgid "Created by" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__create_date +msgid "Created on" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__detail_ids +msgid "Details" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__display_name +msgid "Display Name" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template +msgid "Dms Classification Template" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_dms_classification_template_detail +msgid "Dms Classification Template Detail" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__field_id +msgid "Field" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__id +msgid "ID" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__index +msgid "Index" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__write_date +msgid "Last Updated on" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model_id +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__model_id +msgid "Model" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template__model +msgid "Model name" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__parent_id +msgid "Parent" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__record_ref +msgid "Record Referenced" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_dms_classification_template_detail__sequence +msgid "Sequence" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model.fields,field_description:dms_field_auto_classification.field_wizard_dms_classification_detail__template_id +msgid "Template" +msgstr "" + +#. module: dms_field_auto_classification +#: model:ir.model,name:dms_field_auto_classification.model_wizard_dms_classification_detail +msgid "Wizard Dms Classification Detail" +msgstr "" diff --git a/dms_field_auto_classification/models/__init__.py b/dms_field_auto_classification/models/__init__.py new file mode 100644 index 000000000..cb95092bf --- /dev/null +++ b/dms_field_auto_classification/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import dms_classification_template diff --git a/dms_field_auto_classification/models/dms_classification_template.py b/dms_field_auto_classification/models/dms_classification_template.py new file mode 100644 index 000000000..77fe1b647 --- /dev/null +++ b/dms_field_auto_classification/models/dms_classification_template.py @@ -0,0 +1,46 @@ +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class DmsClassificationTemplate(models.Model): + _inherit = "dms.classification.template" + + model_id = fields.Many2one( + comodel_name="ir.model", string="Model", domain=[("transient", "=", False)] + ) + model = fields.Char( + compute="_compute_model", string="Model name", compute_sudo=True + ) + detail_ids = fields.One2many( + string="Details", + comodel_name="dms.classification.template.detail", + inverse_name="parent_id", + ) + + @api.depends("model_id") + def _compute_model(self): + for item in self: + item.model = item.model_id.model + + +class DmsClassificationTemplateDetail(models.Model): + _name = "dms.classification.template.detail" + _description = "Dms Classification Template Detail" + _order = "sequence, field_id" + + parent_id = fields.Many2one( + comodel_name="dms.classification.template", + string="Parent", + ) + model_id = fields.Many2one( + related="parent_id.model_id", + ) + field_id = fields.Many2one( + comodel_name="ir.model.fields", + domain="[('model_id', '=', model_id)]", + string="Field", + ) + index = fields.Integer(required=True, default=0) + sequence = fields.Integer(required=True, default=10) diff --git a/dms_field_auto_classification/pyproject.toml b/dms_field_auto_classification/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/dms_field_auto_classification/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/dms_field_auto_classification/readme/CONFIGURE.md b/dms_field_auto_classification/readme/CONFIGURE.md new file mode 100644 index 000000000..7a851d3ab --- /dev/null +++ b/dms_field_auto_classification/readme/CONFIGURE.md @@ -0,0 +1,14 @@ +1. Go to Documents / Configuration / Classification Templates and + create or edit a template. +2. You can set a model to which it is linked (res.partner for example). +3. You can define the details to indicate which field is referenced by + the defined filename pattern. + +Full example from res.partner: + +Filename pattern: (\[0-9\]{8}\[A-Z\]).\*.pdf Details: VAT (field) and 0 +(index) Directory Pattern example 1: {0} \> This will attempt to add the +files to the directory linked to the partner with the VAT name. +Directory Pattern example 2: {0} / Misc \> This will attempt to add the +files to the "Misc" subdirectory linked to the partner with the VAT +name. diff --git a/dms_field_auto_classification/readme/CONTRIBUTORS.md b/dms_field_auto_classification/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..5fee39042 --- /dev/null +++ b/dms_field_auto_classification/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [Tecnativa](https://www.tecnativa.com): + - Víctor Martínez + - Pedro M. Baeza diff --git a/dms_field_auto_classification/readme/DESCRIPTION.md b/dms_field_auto_classification/readme/DESCRIPTION.md new file mode 100644 index 000000000..9d3d71216 --- /dev/null +++ b/dms_field_auto_classification/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +Automatically classify files within a .zip file to the corresponding +directory(s) related to an embedded DMS. diff --git a/dms_field_auto_classification/readme/USAGE.md b/dms_field_auto_classification/readme/USAGE.md new file mode 100644 index 000000000..c8bf4ea52 --- /dev/null +++ b/dms_field_auto_classification/readme/USAGE.md @@ -0,0 +1,10 @@ +1. Go to Documents / Auto Classification and select a template and a + .zip file. +2. Press the Analyze button +3. As many lines will be set as the number of files contained in the + .zip file and apply the filename pattern. +4. The record to which they are related (res.partner for example) will + be show on the lines. +5. Press the Classify button +6. The files (dms.file) will be created in the corresponding + directories. diff --git a/dms_field_auto_classification/security/ir.model.access.csv b/dms_field_auto_classification/security/ir.model.access.csv new file mode 100644 index 000000000..e16b79fc4 --- /dev/null +++ b/dms_field_auto_classification/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_dms_classification_template_detail_manager,dms_classification_template_detail_manager,model_dms_classification_template_detail,dms.group_dms_manager,1,1,1,1 diff --git a/dms_field_auto_classification/static/description/icon.png b/dms_field_auto_classification/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9fd5b327acab0e692cbd7160b1f457c092ee3f1d GIT binary patch literal 5069 zcmbtYhf@3+002fEZ4KiK8T%jT zE?@LzU({4C1iiPmg&zPwr|};yjcWE@i~%?in&t=-uNQ~_J6}gYKtO<`i>I5Py`8tC zq?fN#+Qxk@0D$$Wj)s~kGJQQGDE8Ttkk_Q+58VT9PSfQj0XMEFRsQv#>u{MHdv#Az zvy=`0`MDbTIHw|NrxT6fwH7?lh%lc z8>wXhgj9BQy2ar&pmtfp1bk+a3<4XdhA!=w3=#LhhH|kfA01DSXg*cqq6tIsW%yRwQ4gvvW1Tkqf+Q_ zHwwX}lzba#KTgNbzC^9Pip>%E=pv%R_f(x)S161wQfF~zx^b`9riD+p?PN2Y>CE0z zWLH!+r6VP^t;fL|NQ-u4b4+L}VF1%=8W|#P$bud)MjCN*`#iq`@vHCNJnE9?g`@Uo z!kg`^&O;RNz!(iGVSL*i2DC@`{7txpg$RIv&2cG2sk%b3Eb)mkS~By8CxOaY{VH7K z&lMTkGcbMr3oG-_qQvetl!%~nz{UKy@Cpwee#1+e@b;SdljgFq{dIxFR-HU7aqQ)a zfvE0?2(YE>+7Cl#BjR;j_&p?n1Hc|k#!Op{$$^2I@`xJ}98FdQhRZco7N+eYH4&dg zWOEkoCZ0XTP)DmMJ6Ud@kkveNauDG();OyCu94-c4*<*wIY;Brbc4 zIFIX;r0U>y3I1#Mxj0a)(I#qczQ_ z0C_YhBP(a^lZw|C#8?O{7;~~Lb@wgUP-U8OM>_jw<|;u%Xd!*tnYedSY8ZZ%{@%Je_;t>PRD!*8VJRhZ!sxDv&V74V_pl!h&yW21lkFY;K~+du#Xqf; zmqOmyQos2a!;f`N-}QIv;Z=aHjo)n(X9mO7$WU+|C6Ud^#I-Q>ifZ<>>XPMuGfx{7 zomWBcpd#parN~^=IIvX?07Cah|ju z&E%5$!C`*nWib5KcTh>_z17Tl77Y;~2FWAH(*=yI)a^IOCKM)N%tU5-7wLppC^DRn zzr9U`z14HNbCnhl`z+J1Aack*?oZjZ$J^x<>ir6Q<5jiE1c@@muUGZg|5|+=B72PVtSg17*?DneRmElPXFzo z!w@wpRq?m$)1R1%98BdYO3Sk;VatEbRq~tCHqise$G5Ov`?$(;lB=vfKU7U3Js1+> zVL>jB3$DC2|DIPzrl`|2^3c4pP3TGKbLTa)K1C_}YLB!FgWtY?w~+IidJvqcXfQXy zQE%Mq48a)Z1wc+Koqc+*F%=a{yGm;#Y+sKhNl?be*HZSNW{~zb^3w}2z22M!8%PRl zYcA%PlcNrIc6Qi}Mr1MRhdK#o%tx&xnJ5>gNuS}am7x}xpUY3j&iPQXuC_~$RXH)W z$B653cKawGmZhUL-sz{6HM%3BYHKg@pESRjZzqk|m5_>g#k|L?HEOXlX5UdpYLWca zuceZ;RKJvI?_RRK$muuR`+`0W#{v`54hTqe@sb))HNP1iUT{Z0k=u)>FleyU^8WQM z=CBLLJ1$>t8gMmWXtzDsn2e2oqSAh5y!#CI$G$s?=^9IVx`X>^;DE$mBIw->WFPo#3t{tp;? zsG@#%wmLLJ;mz}Lp`&BYiOSqLR9XrHQ~dUCH?OFOKQ~~ir08Qt^c!oKm3Pw-fh%(BZAvi zKrf|sN>N9gJ)cCn4hWTgAcuz}_I>x;6APR5HsYd1SUEl44PI&ZrbIhAlOi29H(xlT z@FC_i{_D6GseiUTve=-&E;zlVIfx!+)pWUVaoH#}{)QlVq_h6brHdqnFVX(HaCmrl z7zX8n@OU}6x>E@z+yDJ5B4M>oCPPgU>EF#K0yJ zj;wS%PXBn2Ow!1>bKMbw1!+V?Ml&!au5SG4^Qbk7(klK&uJO3WL%C5qeRhRHjhb8c zxHu~XWfN}7;5+!}Y*W~iT&O@;t*DMQljN_B>$oTqSgFkw- z)GwoJ$Y`GZ+SIsX~7S?#%tniF&SP} zL%)Cbu}&2G|c4HbP~O zdKNvlF?KWksej;lDxmE_f&Y!cO+S-~K^Q z1HC)x67_^A{W639cB~pXxH)|$#mz!gE~}i(Vihag$qPO4s2`L3*RI-~2Dzq9v3+6V zhq)r&ho0{iyqf*xGz8E$GOSw&ZhaNCZ#p%8@6{X)tJf=OdY9(LUimT`eWClu){?aC z3p2&t%WLZ&8>=u?xvUJCT-gPUc)0TQ7gAjLonsVmL4=m_ud$FV40Bjk@S5d`>w#yg zf5zK5_= zXi)Ehd^U-DbSL|GBd~kV=~36=YAx!kWw_IAk;4h`25DB|$J>orr?Ht2UdxfPw5)^+ z3C`F#*N?rfu5Be13>iH_%ChM>hFzu7jW5sNb&X+soeAHl`RXDgK4vZ1AP4DCZ|u(wY>QtZSavH+(^%#LtZGP1semZ6c3Jp3VKWa zEZO+eaxi6(ZMz-kb&d?Q`GA>G7(kd7?L~iyk&JyzcE)Nr6FnX_;L1esaO+}9>)r!E zY1oUVCp0H2D*I0kvWvu~56>QR+>^hvsXUocI^nkzCxUG|@()3LmQU#APUk2=)y%@! ztv>v$b~{Wg4`Iv;=5WNUfDN)F~EMU`DOaj zfpyD<@r!lYCzboDSJ%?C1|M&)Z>s1)OeWJm>RBM{f)nWUWzA8;g)WBK(yOZ@8jVe3 zRI?Tn19ZPX+cAbB57>uOc&1MV$X7kN_*CwG&CEJy?Q*!Qp_nFluDK`S#Gf|J}kSEdSPdkR$EQoDmHE}s2g?ZmRO-xv^ zIi^3vIJmf;J2=pgp?CCI+oki*y;336)YNdz3e52D2?eXAx`xd;oOZ!(Jwv-tVvqfY zPP4am>^9e9b%U}eX6vP~IY?rXVsdY#+o+^(eeY&933NAw;j!xJZpo&{SVguZ5paKB zYC7W}mG1;8rApbFS{WTYLGWE5D>AQvV^E1tW5v7YDQmRGSiRp80=*TMpAYeDIealV zY=iw#Q2Q;{Une6YW5TyrBJ)$J3s=B&i2M=NK}{MXypeeQ=V0d|HpJ22%j`=8Be(gJ z){?WsF{0yD3ApD~;(AhNxpre-vnZ^)$lkAo#})HqO!<`D624{!Y|a zLGZSuX`3oX2ta>}WAEt|Ti78T497o^E>N;Y-od)|ybBe=7r}2KFN(B=Kmr$@FhF-_ z@?;{<{a_6bK6;z z71TD^{i3Jt)b+L;Zq;>T~3N1h;3Mz-2zS*1qLA zjAE}bhdJ_AU3E3SKTY0dKQ#}X94~0z)%N8CZ)-LBtI|HQp#ADz*ILWs;*NfQ>!Mhz zWvypkWo65FlB9o9v$S;3tvV7Z%kAQ-iBl5ts}w*FLE{2Q$CGH?agrPqS`l)*uPHe) z?4U_U4FY27mNJ9BL*tY->T*5(o+WYjJ{Nb%M!bBv-Ez=ezG}3L@54q*JAM)$shmS6 zm_~xN=76HH9c;!N(YHTIvpmuM?=8@#{fr-zQ1=ih=!5w`EBG^a^}JY1=*M=;>Gxff z%LMgzuZ zWyO~br+imyznR!;=TMdDE=qbXlF>9?LV(gzAudyWJ~~B1`xDpAdXZwWz-8=14f68t z@m8^zkCm{;I0r5~ZhXE#$PtR2_An%4nJ1P6^VeuhN63#ttPEtf)VJE5x%wLn)ab;! zTh)Lj^owS1^F=78_>Tk@L-|LcC5DJ1 z*7Aa@#K}j8>dtvg`F=VM3I3b`eaHo5z5O5eA0eS1EL16mwIW(gkf1R*+>Egh>0#6PMSrCN^Pn2-UBWr6EtEV5NCKHZRB#=DnncXce6=`v zX90RGNJHDr@0F?}>oo2pgHu=26|6_65FL0TOB@-C^FdM&@tDu^hM+Lt46U1M(Ax>y zo7QDjVD!yJ1=ugS2$0ljYECf!tOC<=%?;*MEmI@9LRTLj@XZC5w|Gra+xLQcx^xit zG<3e>Y4&&3+W?z}#iZ_1#1Xmw!80;lu^0+2u|YMnHLTZzSFexhWT9&J(M}fVrTG}y zZbNB2Jyum(Z@Vt%ag-??%cS`xiZJ}qg`!&wP8b#sqInH6e=SOLZye$=E_6v2M48Gh zZwXX=Mg(ld$WQz`beB7(&jQWf{8%jZJMX$ql7zuZ)}q3~C3T~_LlF_ey!Q~xev58X zvu*RWQ@`zn6XQ9O{A(S@m2se + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dms_field_auto_classification/static/description/index.html b/dms_field_auto_classification/static/description/index.html new file mode 100644 index 000000000..5228543c9 --- /dev/null +++ b/dms_field_auto_classification/static/description/index.html @@ -0,0 +1,470 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Auto classify files into embedded DMS

+ +

Beta License: AGPL-3 OCA/dms Translate me on Weblate Try me on Runboat

+

Automatically classify files within a .zip file to the corresponding +directory(s) related to an embedded DMS.

+

Table of contents

+ +
+

Configuration

+
    +
  1. Go to Documents / Configuration / Classification Templates and create +or edit a template.
  2. +
  3. You can set a model to which it is linked (res.partner for example).
  4. +
  5. You can define the details to indicate which field is referenced by +the defined filename pattern.
  6. +
+

Full example from res.partner:

+

Filename pattern: ([0-9]{8}[A-Z]).*.pdf Details: VAT (field) and 0 +(index) Directory Pattern example 1: {0} > This will attempt to add the +files to the directory linked to the partner with the VAT name. +Directory Pattern example 2: {0} / Misc > This will attempt to add the +files to the “Misc” subdirectory linked to the partner with the VAT +name.

+
+
+

Usage

+
    +
  1. Go to Documents / Auto Classification and select a template and a +.zip file.
  2. +
  3. Press the Analyze button
  4. +
  5. As many lines will be set as the number of files contained in the +.zip file and apply the filename pattern.
  6. +
  7. The record to which they are related (res.partner for example) will +be show on the lines.
  8. +
  9. Press the Classify button
  10. +
  11. The files (dms.file) will be created in the corresponding +directories.
  12. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Víctor Martínez
    • +
    • Pedro M. Baeza
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

victoralmau

+

This module is part of the OCA/dms project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/dms_field_auto_classification/tests/__init__.py b/dms_field_auto_classification/tests/__init__.py new file mode 100644 index 000000000..0be859d20 --- /dev/null +++ b/dms_field_auto_classification/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from . import test_dms_field_auto_classification diff --git a/dms_field_auto_classification/tests/data/test.zip b/dms_field_auto_classification/tests/data/test.zip new file mode 100644 index 0000000000000000000000000000000000000000..155578038e3eb9c70819744f2fd3821da58f0cfa GIT binary patch literal 2902 zcmeH}`#aMM7{I@?%W;WnD?{Wcq*OQ3@3gBpq;X|y_YThB3ks_7VM_0Jzv&FL47!)BYU457<2g2S0Z zu*dhv(ZQoejd#<7d0IY?*@Ur4)ELxQo_}0glBD3Atj;y2(4awikELt)^Ba|+ZS%Uq37shd(huAJ*4Cu{F*&}# z4JTAy7`WCG^#_-h_Ija()R-16LO33)p3iZtp}pHi3vXCD3@2*Mv|rWKg>zFtAEU6K zn3Gw}HY6!peJ7rl%ry-fprJW8MWYHUI_fYbvNt22M`#-ktkuEbwv9Js;Vvh&)Y)5* z8(b2@?wl<5i18MXb}hf%PrtX1V5PcnkW!|yvtz?2zBnXX%2HSUr>Aqf{uCM;<*SGc zn{jiP9l9DbdIQOUpeCiNG|fF{u7GW%H14~Cp%N_0jRYk(AB6J`R`F~=6s<+!wyoCv z5AWZke5^RC=MABq&JE4eK=^6Iq(5<%8+2o(CzQg*Ga}!1T*oW9{oK#~0>*oO`$ZRs z@%=iEG8Q6_e{vC9JUw#2JjbNF7&YP>_essDmkezvWYML4-$P^{*Tty^V{bj^rl?xW z?V(5&JA%$pP#lon1e$-kZk)TErQM=x_;IRVH%wiWR^2s73_TPQsjda z(u`cf)8x2iT zRjScYpnI?@X$3#oGwHI6I&Uafp`cXZls}O}-w|WbVG`Gcj*sZep1_J$BrDNT-JY!o zcEmH3#*>(Hvs0i+C{=S~03Lo=3o>aJi5^XDF@{SDC6*+U>naqaO^9C9jZffU!fOkE zMP2ljm!s-renng6j3}2NAD7n5`LH~j$2qs0f`AecljTnA8FBXRwi8~4${{xGZCzIi zU!OW&FE(99ZB>^(8{LvSp|W#2bqIFvLXB)a&1%FeL1?i1Q9qoQ_wTM0J z7%mbR5}6(k7m<%@=FW;E?Tbx&o6cJ(DFPy_@(8tz6WL*;Dtt+}?WEtQk0O@VULz@p zDjixE-KCS;{B25lWCr5t&x$i%yKYKqlJsz871?nzr}*{{98lsxH>%l%QvS9Q&QLoS zi7eJ$sZ6#p+)|IWj*q4XRey%&UqzX}?Hqf$Q1ng7&b%r$fa$v-n1$5W<%09oTBG{m zqXO7j@RUh^hBzeeM3HUbv~p6EAJ+eI*+rM~#)}!&=3~EOAbgk8^7H)C@MLa5qI-kP zXX8vc(iRL+BGhMllT$Z3sAfwRWZ-u8m}NCaOTIkK9yY)mZ@mw3^k?-={UE3jN1a*I259?f*S#w<^JFc>SZ3P}ukX601?P+HXmJ G-~Iwb?m4ai literal 0 HcmV?d00001 diff --git a/dms_field_auto_classification/tests/test_dms_field_auto_classification.py b/dms_field_auto_classification/tests/test_dms_field_auto_classification.py new file mode 100644 index 000000000..720047d4f --- /dev/null +++ b/dms_field_auto_classification/tests/test_dms_field_auto_classification.py @@ -0,0 +1,193 @@ +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from base64 import b64encode +from os import path + +from odoo.tests import Form, new_test_user +from odoo.tests.common import users + +from odoo.addons.base.tests.common import BaseCommon + + +class TestDmsFieldAutoClassification(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, test_dms_field=True)) + cls.template = cls.env.ref( + "dms_field_auto_classification.dms_classification_template_partners" + ) + cls.user = new_test_user( + cls.env, login="test_dms_manager_user", groups="dms.group_dms_manager" + ) + access_group = cls.env.ref("dms.access_group_01_demo") + access_group.explicit_user_ids = [(4, cls.user.id)] + cls.env.ref("dms_field.field_template_partner").unlink() + file_template = cls.env["dms.field.template"].create( + { + "name": "Test partner template", + "storage_id": cls.env.ref("dms.storage_demo").id, + "model_id": cls.env.ref("base.model_res_partner").id, + "group_ids": [(4, access_group.id)], + "directory_format_name": "{{object.vat}}", + } + ) + file_template_ctx = file_template.with_context( + res_model=file_template._name, res_id=file_template.id + ) + file_template_ctx.create_dms_directory() + file_template_ctx.invalidate_model() + cls.partner_a = cls.env["res.partner"].create( + { + "name": "Test partner A", + "vat": "82326033V", + } + ) + cls.partner_b = cls.env["res.partner"].create( + { + "name": "Test partner B", + "vat": "58426469Y", + } + ) + cls.wizard = cls._create_wizard_dms_classification(cls.template) + cls.extra_wizard = cls._create_wizard_dms_classification(cls.template) + + @classmethod + def _data_file(cls, filename, encoding=None): + mode = "rt" if encoding else "rb" + with open(path.join(path.dirname(__file__), filename), mode) as file: + data = file.read() + if encoding: + data = data.encode(encoding) + return b64encode(data) + + @classmethod + def _create_wizard_dms_classification(cls, template): + wizard_form = Form(cls.env["wizard.dms.classification"]) + wizard_form.template_id = template + wizard_form.data_file = cls._data_file("data/test.zip") + return wizard_form.save() + + def test_partner_misc(self): + self.assertEqual(self.partner_a.dms_directory_ids.name, "82326033V") + self.assertEqual(self.partner_b.dms_directory_ids.name, "58426469Y") + + @users("test_dms_manager_user") + def test_wizard_dms_clasification_process(self): + self.wizard = self.wizard.with_user(self.env.user) + self.assertEqual(self.wizard.state, "draft") + # Wizard - Analyze process + self.wizard.action_analyze() + self.assertEqual(self.wizard.state, "analyze") + self.assertEqual(len(self.wizard.detail_ids), 2) + file_names = self.wizard.mapped("detail_ids.file_name") + self.assertIn("82326033V.pdf", file_names) + self.assertIn("58426469Y.pdf", file_names) + detail_1 = self.wizard.detail_ids.filtered( + lambda x: x.file_name == "82326033V.pdf" + ) + self.assertEqual(detail_1.state, "to_classify") + self.assertEqual(detail_1.directory_id, self.partner_a.dms_directory_ids) + self.assertEqual(detail_1.record_ref, self.partner_a) + detail_2 = self.wizard.detail_ids.filtered( + lambda x: x.file_name == "58426469Y.pdf" + ) + self.assertEqual(detail_2.state, "to_classify") + self.assertEqual(detail_2.directory_id, self.partner_b.dms_directory_ids) + self.assertEqual(detail_2.record_ref, self.partner_b) + # Wizard - Classify process + res = self.wizard.action_classify() + dms_files = self.env[res["res_model"]].search(res["domain"]) + self.assertEqual(len(dms_files), 2) + self.assertEqual( + detail_1.file_id.directory_id, self.partner_a.dms_directory_ids + ) + self.assertEqual(detail_1.file_id.name, "82326033V.pdf") + self.assertTrue(detail_1.file_id.content) + self.assertEqual(detail_1.file_id.res_model, self.partner_a._name) + self.assertEqual(detail_1.file_id.res_id, self.partner_a.id) + self.assertEqual( + detail_2.file_id.directory_id, self.partner_b.dms_directory_ids + ) + self.assertEqual(detail_2.file_id.name, "58426469Y.pdf") + self.assertTrue(detail_2.file_id.content) + self.assertEqual(detail_2.file_id.res_model, self.partner_b._name) + self.assertEqual(detail_2.file_id.res_id, self.partner_b.id) + # Extra wizard + self.extra_wizard = self.extra_wizard.with_user(self.env.user) + self.assertEqual(self.extra_wizard.state, "draft") + # New Wizard - Analyze process + self.extra_wizard.action_analyze() + self.assertEqual(self.extra_wizard.state, "analyze") + self.assertEqual(len(self.extra_wizard.detail_ids), 2) + file_names = self.extra_wizard.mapped("detail_ids.file_name") + self.assertIn("82326033V.pdf", file_names) + self.assertIn("58426469Y.pdf", file_names) + detail_1 = self.extra_wizard.detail_ids.filtered( + lambda x: x.file_name == "82326033V.pdf" + ) + self.assertEqual(detail_1.directory_id, self.partner_a.dms_directory_ids) + self.assertTrue(detail_1.file_id) + self.assertEqual(detail_1.record_ref, self.partner_a) + self.assertEqual(detail_1.state, "classified") + detail_2 = self.extra_wizard.detail_ids.filtered( + lambda x: x.file_name == "58426469Y.pdf" + ) + self.assertEqual(detail_2.directory_id, self.partner_b.dms_directory_ids) + self.assertTrue(detail_2.file_id) + self.assertEqual(detail_2.record_ref, self.partner_b) + self.assertEqual(detail_1.state, "classified") + # New Wizard - Classify process + res = self.extra_wizard.action_classify() + new_dms_files = self.env[res["res_model"]].search(res["domain"]) - dms_files + self.assertEqual(len(new_dms_files), 0) + + @users("test_dms_manager_user") + def test_wizard_dms_clasification_process_filename_pattern(self): + self.template.filename_pattern = self.template.filename_pattern.replace( + ".pdf", ".txt" + ) + self.assertEqual(self.wizard.state, "draft") + self.wizard = self.wizard.with_user(self.env.user) + self.wizard.action_analyze() + self.assertEqual(self.wizard.state, "analyze") + self.assertEqual(len(self.wizard.detail_ids), 0) + + @users("test_dms_manager_user") + def test_wizard_dms_clasification_process_subdirectory(self): + self.template.directory_pattern = "{0} / Misc" + # Create subdirectory to partner a + directory_misc_a = self.env["dms.directory"].create( + {"name": "Misc", "parent_id": self.partner_a.dms_directory_ids.id} + ) + # Create subdirectory to partner b + directory_misc_b = self.env["dms.directory"].create( + {"name": "Misc", "parent_id": self.partner_b.dms_directory_ids.id} + ) + # Wizard - Analyze process + self.wizard = self.wizard.with_user(self.env.user) + self.wizard.action_analyze() + self.assertEqual(self.wizard.state, "analyze") + self.assertEqual(len(self.wizard.detail_ids), 2) + file_names = self.wizard.mapped("detail_ids.file_name") + self.assertIn("82326033V.pdf", file_names) + self.assertIn("58426469Y.pdf", file_names) + detail_1 = self.wizard.detail_ids.filtered( + lambda x: x.file_name == "82326033V.pdf" + ) + self.assertEqual(detail_1.state, "to_classify") + self.assertEqual(detail_1.directory_id, directory_misc_a) + self.assertEqual(detail_1.record_ref, self.partner_a) + detail_2 = self.wizard.detail_ids.filtered( + lambda x: x.file_name == "58426469Y.pdf" + ) + self.assertEqual(detail_2.state, "to_classify") + self.assertEqual(detail_2.directory_id, directory_misc_b) + self.assertEqual(detail_2.record_ref, self.partner_b) + # Wizard - Classify process + res = self.wizard.action_classify() + dms_files = self.env[res["res_model"]].search(res["domain"]) + self.assertEqual(len(dms_files), 2) + self.assertEqual(detail_1.file_id.directory_id, directory_misc_a) + self.assertEqual(detail_2.file_id.directory_id, directory_misc_b) diff --git a/dms_field_auto_classification/views/dms_classification_template_views.xml b/dms_field_auto_classification/views/dms_classification_template_views.xml new file mode 100644 index 000000000..a91a70347 --- /dev/null +++ b/dms_field_auto_classification/views/dms_classification_template_views.xml @@ -0,0 +1,37 @@ + + + + dms.classification.template tree + dms.classification.template + + + + + + + + + dms.classification.template form + dms.classification.template + + + + + + + + + + + + + + + + diff --git a/dms_field_auto_classification/wizards/__init__.py b/dms_field_auto_classification/wizards/__init__.py new file mode 100644 index 000000000..4ba9cc23a --- /dev/null +++ b/dms_field_auto_classification/wizards/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import wizard_dms_classification diff --git a/dms_field_auto_classification/wizards/wizard_dms_classification.py b/dms_field_auto_classification/wizards/wizard_dms_classification.py new file mode 100644 index 000000000..63567351c --- /dev/null +++ b/dms_field_auto_classification/wizards/wizard_dms_classification.py @@ -0,0 +1,83 @@ +# Copyright 2024-2026 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import re + +from odoo import api, fields, models + + +class WizardDmsClassificationDetail(models.TransientModel): + _inherit = "wizard.dms.classification.detail" + + template_id = fields.Many2one(related="parent_id.template_id") + record_ref = fields.Reference( + string="Record Referenced", + compute="_compute_record_ref", + selection=lambda self: self._get_ref_selection(), + ) + + @api.model + def _get_ref_selection(self): + models = self.env["ir.model"].sudo().search([("transient", "=", False)]) + return [(model.model, model.name) for model in models] + + @api.depends( + "directory_id", + "directory_id.root_directory_id", + "directory_id.res_model", + "directory_id.res_id", + ) + def _compute_record_ref(self): + """Set the linked record according to directory or root directory (to be + correct if we are in a subdirectory).""" + for record in self: + directory = False + if record.directory_id.res_model and record.directory_id.res_id: + directory = record.directory_id + elif ( + record.directory_id.parent_id.res_model + and record.directory_id.parent_id.res_id + ): + directory = record.directory_id.parent_id + elif ( + record.directory_id.root_directory_id.res_model + and record.directory_id.root_directory_id.res_id + ): + directory = record.directory_id.root_directory_id + record.record_ref = ( + f"{directory.res_model},{directory.res_id}" if directory else False + ) + + @api.depends("file_name", "template_id.model_id") + def _compute_directory_id(self): + """Overwrite to redefine the directory if the template has a linked model.""" + self_with_model = self.filtered(lambda x: x.template_id.model_id) + _self = self - self_with_model + res = super(WizardDmsClassificationDetail, _self)._compute_directory_id() + directory_model = self.env["dms.directory"] + for item in self_with_model: + domain = [ + ("company_id", "=", self.parent_id.company_id.id), + ("res_model", "=", item.template_id.model_id.model), + ("res_id", ">", 0), + ] + directories = directory_model.search(domain) + # We also add the subdirectorior because they are necessary and have not + # set res_model and res_id + directories += directories.mapped("child_directory_ids") + if directories: + matches = re.search(item.template_id.filename_pattern, item.file_name) + if matches: + directory_pattern = item.template_id.directory_pattern + for detail in item.template_id.detail_ids: + matches_value = matches.groups()[detail.index] + # Change directory pattern if index in pattern + expected = f"{{{detail.index}}}" + if expected in directory_pattern: + directory_pattern = directory_pattern.replace( + expected, matches_value + ) + # Search directories according to directory_pattern + item.directory_id = self.parent_id._get_directory_from_pattern( + directory_pattern, directories + ) + return res diff --git a/dms_field_auto_classification/wizards/wizard_dms_classification_views.xml b/dms_field_auto_classification/wizards/wizard_dms_classification_views.xml new file mode 100644 index 000000000..a7a91ad61 --- /dev/null +++ b/dms_field_auto_classification/wizards/wizard_dms_classification_views.xml @@ -0,0 +1,19 @@ + + + + wizard.dms.classification form + wizard.dms.classification + + + + + + + + From 95d01a995fcbd6d7401db93aae86b5d32f5634d4 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Tue, 26 May 2026 22:01:02 -0400 Subject: [PATCH 4/8] [IMP] dms_field_auto_classification: pre-commit auto fixes --- dms_field_auto_classification/README.rst | 8 ++++---- dms_field_auto_classification/__manifest__.py | 2 +- .../static/description/index.html | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dms_field_auto_classification/README.rst b/dms_field_auto_classification/README.rst index 88012d0b8..f9eb092d7 100644 --- a/dms_field_auto_classification/README.rst +++ b/dms_field_auto_classification/README.rst @@ -21,13 +21,13 @@ Auto classify files into embedded DMS :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fdms-lightgray.png?logo=github - :target: https://github.com/OCA/dms/tree/18.0/dms_field_auto_classification + :target: https://github.com/OCA/dms/tree/19.0/dms_field_auto_classification :alt: OCA/dms .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/dms-18-0/dms-18-0-dms_field_auto_classification :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/dms&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/dms&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -78,7 +78,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -119,6 +119,6 @@ Current `maintainer `__: |maintainer-victoralmau| -This module is part of the `OCA/dms `_ project on GitHub. +This module is part of the `OCA/dms `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/dms_field_auto_classification/__manifest__.py b/dms_field_auto_classification/__manifest__.py index f4e7154f8..7b9e61324 100644 --- a/dms_field_auto_classification/__manifest__.py +++ b/dms_field_auto_classification/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "Auto classify files into embedded DMS", - "version": "18.0.1.0.1", + "version": "19.0.1.0.0", "category": "Document Management", "website": "https://github.com/OCA/dms", "author": "Tecnativa, Odoo Community Association (OCA)", diff --git a/dms_field_auto_classification/static/description/index.html b/dms_field_auto_classification/static/description/index.html index 5228543c9..3268e0a7b 100644 --- a/dms_field_auto_classification/static/description/index.html +++ b/dms_field_auto_classification/static/description/index.html @@ -374,7 +374,7 @@

Auto classify files into embedded DMS

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:08d9cc488429baf098763c68704aa8516ffc5b2f1ff40c2b7422db0be35d39ff !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/dms Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/dms Translate me on Weblate Try me on Runboat

Automatically classify files within a .zip file to the corresponding directory(s) related to an embedded DMS.

Table of contents

@@ -428,7 +428,7 @@

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -460,7 +460,7 @@

Maintainers

promote its widespread use.

Current maintainer:

victoralmau

-

This module is part of the OCA/dms project on GitHub.

+

This module is part of the OCA/dms project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

From 388c436c74b37fdcc4a1d105109309b80e2c81e1 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Tue, 26 May 2026 22:12:16 -0400 Subject: [PATCH 5/8] [MIG] dms_field_auto_classification: Migration to 19.0 --- .../test_dms_field_auto_classification.py | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/dms_field_auto_classification/tests/test_dms_field_auto_classification.py b/dms_field_auto_classification/tests/test_dms_field_auto_classification.py index 720047d4f..e3275d917 100644 --- a/dms_field_auto_classification/tests/test_dms_field_auto_classification.py +++ b/dms_field_auto_classification/tests/test_dms_field_auto_classification.py @@ -15,19 +15,44 @@ class TestDmsFieldAutoClassification(BaseCommon): def setUpClass(cls): super().setUpClass() cls.env = cls.env(context=dict(cls.env.context, test_dms_field=True)) - cls.template = cls.env.ref( - "dms_field_auto_classification.dms_classification_template_partners" - ) cls.user = new_test_user( cls.env, login="test_dms_manager_user", groups="dms.group_dms_manager" ) - access_group = cls.env.ref("dms.access_group_01_demo") - access_group.explicit_user_ids = [(4, cls.user.id)] - cls.env.ref("dms_field.field_template_partner").unlink() + # OCA CI runs without demo data — create all fixtures inline. + storage = cls.env["dms.storage"].create( + {"name": "Test Storage", "save_type": "database"} + ) + access_group = cls.env["dms.access.group"].create( + { + "name": "Test DMS Access Group", + "perm_create": True, + "perm_write": True, + "perm_unlink": True, + "explicit_user_ids": [(4, cls.user.id)], + } + ) + # Create the classification template (extends dms.classification.template + # with model_id / detail_ids added by this module). + cls.template = cls.env["dms.classification.template"].create( + { + "name": "Partners template", + "filename_pattern": "([0-9]{8}[A-Z]).*.pdf", + "model_id": cls.env.ref("base.model_res_partner").id, + "directory_pattern": "{0}", + } + ) + cls.env["dms.classification.template.detail"].create( + { + "parent_id": cls.template.id, + "field_id": cls.env.ref("base.field_res_partner__vat").id, + "index": 0, + } + ) + # DMS field template for res.partner — directories named by VAT. file_template = cls.env["dms.field.template"].create( { "name": "Test partner template", - "storage_id": cls.env.ref("dms.storage_demo").id, + "storage_id": storage.id, "model_id": cls.env.ref("base.model_res_partner").id, "group_ids": [(4, access_group.id)], "directory_format_name": "{{object.vat}}", From 242e4c47b5fb838bdf00a7386c5c259600585b57 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Thu, 28 May 2026 15:27:46 -0400 Subject: [PATCH 6/8] [IMP] dms_field_auto_classification: post-migration review fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wizard: - _compute_directory_id: use item.parent_id instead of self.parent_id inside the recordset loop — self is the full wizard set, not the current item; caused silent wrong-company + empty pattern results on multi-record batches - _compute_file_id: replace removed fields.first(files) with files[:1] dms_field _search_parents (Odoo 19 SQL composable API): - _where_calc(), _apply_ir_rules(), and tuple query.from_clause/where_clause were removed in 19.0; rewrote using _search(bypass_access=True) + SQL() composable objects — was an AttributeError on every jstree load dms_field tests: - res.groups field renamed users → user_ids in 19.0 - fields.first() removed — replaced with recordset[:1] - test_creation_process_*_with_parent: template root dir as parent_directory_id caused infinite recursion in _create_child_directories; use independent dir - new tests: test_search_parents_count, limit_offset, excludes_children dms / dms_field UX (code-review): - dms_directory.scss / portal.scss: DRY bucket colour maps with @each loops; portal map was declared after first use (silent no-op in SCSS) - portal.xml: replace broken icon_url img with initials chip (data-bucket hash) - file_preview_pane.xml: ARIA attrs (role, aria-label, aria-busy, aria-live) - file_kanban_record.esm.js: .o_kanban_dms_card--previewing selection ring - dms_list widget: structured directory preview card (chip + 4-stat grid); Bootstrap CSS variable tokens for dark-mode; fix #aaaa invalid hex --- .../components/preview/file_preview_pane.xml | 14 +- .../src/js/views/file_kanban_record.esm.js | 10 + dms/static/src/scss/dms_directory.scss | 250 ++++++++++-------- dms/static/src/scss/dms_ext_palette.scss | 81 +++++- dms/static/src/scss/file_kanban.scss | 219 +++++++++------ dms/static/src/scss/file_preview_pane.scss | 33 ++- dms/static/src/scss/portal.scss | 129 +++++++-- dms/template/portal.xml | 7 +- .../wizards/wizard_dms_classification.py | 2 +- dms_field/models/dms_directory.py | 71 ++--- .../static/src/views/dms_list/dms_list.scss | 13 +- .../src/views/dms_list/dms_list_renderer.scss | 73 ++++- .../src/views/dms_list/dms_list_renderer.xml | 79 +++--- dms_field/tests/test_dms_field.py | 65 ++++- dms_field_auto_classification/README.rst | 2 +- .../wizards/wizard_dms_classification.py | 6 +- 16 files changed, 716 insertions(+), 338 deletions(-) diff --git a/dms/static/src/js/components/preview/file_preview_pane.xml b/dms/static/src/js/components/preview/file_preview_pane.xml index 1162c2181..0610a8d25 100644 --- a/dms/static/src/js/components/preview/file_preview_pane.xml +++ b/dms/static/src/js/components/preview/file_preview_pane.xml @@ -7,7 +7,11 @@