From 6cf79aefee9ca02898426504b353be5e289b0fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Mombereau?= Date: Tue, 24 Dec 2024 14:49:47 -0300 Subject: [PATCH 1/7] [ADD] new module project_customer_access --- project_customer_access/README.rst | 34 ++ project_customer_access/__init__.py | 1 + project_customer_access/__manifest__.py | 25 ++ .../data/project_customer_access_demo.xml | 18 + project_customer_access/data/res_groups.xml | 18 + project_customer_access/models/__init__.py | 1 + project_customer_access/models/project.py | 79 +++++ .../security/ir.model.access.csv | 6 + .../project_customer_access_security.xml | 28 ++ .../static/description/icon.svg | 1 + project_customer_access/tests/__init__.py | 1 + .../tests/test_project_customer_access.py | 128 ++++++++ .../views/project_task_views.xml | 310 ++++++++++++++++++ .../odoo/addons/project_customer_access | 1 + setup/project_customer_access/setup.py | 6 + 15 files changed, 657 insertions(+) create mode 100644 project_customer_access/README.rst create mode 100644 project_customer_access/__init__.py create mode 100644 project_customer_access/__manifest__.py create mode 100644 project_customer_access/data/project_customer_access_demo.xml create mode 100644 project_customer_access/data/res_groups.xml create mode 100644 project_customer_access/models/__init__.py create mode 100644 project_customer_access/models/project.py create mode 100644 project_customer_access/security/ir.model.access.csv create mode 100644 project_customer_access/security/project_customer_access_security.xml create mode 100644 project_customer_access/static/description/icon.svg create mode 100644 project_customer_access/tests/__init__.py create mode 100644 project_customer_access/tests/test_project_customer_access.py create mode 100644 project_customer_access/views/project_task_views.xml create mode 120000 setup/project_customer_access/odoo/addons/project_customer_access create mode 100644 setup/project_customer_access/setup.py diff --git a/project_customer_access/README.rst b/project_customer_access/README.rst new file mode 100644 index 0000000..42dae3a --- /dev/null +++ b/project_customer_access/README.rst @@ -0,0 +1,34 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +========================= +Project Customer Access +========================= + +Project menu and views for customers accessing your own ERP. +Module based on : +- `cross_connect_server `_ to make the connection between the two ERP +- `base_group_backend `_ to give restricted backend access to the customer. + + +Configuration +============= + +The customer's user needs to be part of one of the security groups "Project Access Customer" to access its Tasks with restricted access. + +As these groups are based on "User types / Backend UI user", the only way to add the user to these groups through the UI will be through the group's form view itself, changing the "Users" tab. + + +Usage +===== + +#. Create a user with the security group "Project Access Customer / User" or "Manager" +#. Relate the user and the projects he needs to access to the same "Cross Connect Client" +#. Connect as the customer's and go to its "Project" menu + + +Contributors +------------ + +* Clément Mombereau diff --git a/project_customer_access/__init__.py b/project_customer_access/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/project_customer_access/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/project_customer_access/__manifest__.py b/project_customer_access/__manifest__.py new file mode 100644 index 0000000..182f3d3 --- /dev/null +++ b/project_customer_access/__manifest__.py @@ -0,0 +1,25 @@ +# Copyright 2024 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Project Customer Access", + "summary": """Project menu and views for customers accessing your own ERP""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Akretion", + "website": "http://akretion.com", + "depends": [ + "project", + # https://github.com/OCA/server-backend + "base_group_backend", + # https://github.com/OCA/server-auth + "cross_connect_server", + ], + "data": [ + "data/res_groups.xml", + "security/ir.model.access.csv", + "security/project_customer_access_security.xml", + "views/project_task_views.xml", + ], + "demo": ["data/project_customer_access_demo.xml"], +} diff --git a/project_customer_access/data/project_customer_access_demo.xml b/project_customer_access/data/project_customer_access_demo.xml new file mode 100644 index 0000000..b1efc41 --- /dev/null +++ b/project_customer_access/data/project_customer_access_demo.xml @@ -0,0 +1,18 @@ + + + + + + Cross Connect Server Endpoint Demo + /api + cross_connect + + + + Test Client + + server-api-key + + + + \ No newline at end of file diff --git a/project_customer_access/data/res_groups.xml b/project_customer_access/data/res_groups.xml new file mode 100644 index 0000000..689a58d --- /dev/null +++ b/project_customer_access/data/res_groups.xml @@ -0,0 +1,18 @@ + + + + Project Access Customer + 90 + + + User + + + + + Manager + + + + + \ No newline at end of file diff --git a/project_customer_access/models/__init__.py b/project_customer_access/models/__init__.py new file mode 100644 index 0000000..351a3ad --- /dev/null +++ b/project_customer_access/models/__init__.py @@ -0,0 +1 @@ +from . import project diff --git a/project_customer_access/models/project.py b/project_customer_access/models/project.py new file mode 100644 index 0000000..a133ff9 --- /dev/null +++ b/project_customer_access/models/project.py @@ -0,0 +1,79 @@ +# Copyright 2024 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json + +from lxml import etree +from odoo import api, fields, models + + +class ProjectProject(models.Model): + _inherit = "project.project" + + cross_connect_client_id = fields.Many2one("cross.connect.client") + + +class ProjectTask(models.Model): + _inherit = "project.task" + + def _get_customer_access_view_ids(self): + form_id = self.env.ref("project_customer_access.view_task_form") + kanban_id = self.env.ref("project_customer_access.view_task_kanban") + return form_id | kanban_id + + def _get_editable_fields_customer(self): + return ["name", "description"] + + def _get_editable_fields_manager(self): + return ["name", "description", "tag_ids", "stage_id", "project_id"] + + def _get_readonly_value(self, field): + field_name = field.attrib.get("name") + + if self.env.user.has_group("project_customer_access.group_manager"): + return field_name not in self._get_editable_fields_manager() + + elif self.env.user.has_group("project_customer_access.group_customer"): + if field_name == "project_id": + # project_id must be readonly only after creation + return [("create_date", "!=", False)] + elif field_name in self._get_editable_fields_customer(): + # Allows the customers who are not manager to modify only their tasks + return [("user_ids", "not in", self.env.user.id)] + else: + return True + + else: + return True + + @api.model + def get_view(self, view_id=None, view_type="form", **options): + res = super().get_view(view_id=view_id, view_type=view_type, **options) + customer_access_view_ids = self._get_customer_access_view_ids() + + if view_id in customer_access_view_ids.ids: + doc = etree.XML(res["arch"]) + + for field in doc.xpath("//field[@name][not(ancestor::field)]"): + modifiers = json.loads( + field.attrib.get("modifiers", '{"readonly": false}') + ) + if modifiers.get("readonly") is not True: + modifiers["readonly"] = self._get_readonly_value(field) + + field.attrib["modifiers"] = json.dumps(modifiers) + + res["arch"] = etree.tostring(doc, pretty_print=True) + + return res + + def write(self, values): + if ( + self.env.user.has_group("project_customer_access.group_manager") + and "project_id" in values + ): + # We need to give sudo access to the manager to be able to change project_id + # on the task's timesheets even if they are not its own. + self.env = self.sudo().env + + return super().write(values) diff --git a/project_customer_access/security/ir.model.access.csv b/project_customer_access/security/ir.model.access.csv new file mode 100644 index 0000000..ee1f250 --- /dev/null +++ b/project_customer_access/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_customer_project,access_customer_project,project.model_project_project,project_customer_access.group_customer,1,0,0,0 +access_customer_task,access_customer_task,project.model_project_task,project_customer_access.group_customer,1,1,1,0 +access_customer_task_type,access_customer_task_type,project.model_project_task_type,project_customer_access.group_customer,1,0,0,0 +access_customer_internal_task_type,access_customer_internal_task_type,project.model_project_task_stage_personal,project_customer_access.group_customer,1,1,0,0 +access_customer_task_rating,access_customer_task_rating,rating.model_rating_rating,project_customer_access.group_customer,1,0,0,0 \ No newline at end of file diff --git a/project_customer_access/security/project_customer_access_security.xml b/project_customer_access/security/project_customer_access_security.xml new file mode 100644 index 0000000..0722835 --- /dev/null +++ b/project_customer_access/security/project_customer_access_security.xml @@ -0,0 +1,28 @@ + + + + + + Project Customer Access: Tasks + + + [('project_id.cross_connect_client_id', '=', user.cross_connect_client_id.id)] + + + + + + + + + + Project Customer Access: Projects + + + [('cross_connect_client_id', '=', user.cross_connect_client_id.id)] + + + + + + diff --git a/project_customer_access/static/description/icon.svg b/project_customer_access/static/description/icon.svg new file mode 100644 index 0000000..7fd6ba9 --- /dev/null +++ b/project_customer_access/static/description/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/project_customer_access/tests/__init__.py b/project_customer_access/tests/__init__.py new file mode 100644 index 0000000..fb93ebf --- /dev/null +++ b/project_customer_access/tests/__init__.py @@ -0,0 +1 @@ +from . import test_project_customer_access diff --git a/project_customer_access/tests/test_project_customer_access.py b/project_customer_access/tests/test_project_customer_access.py new file mode 100644 index 0000000..242a0a0 --- /dev/null +++ b/project_customer_access/tests/test_project_customer_access.py @@ -0,0 +1,128 @@ +# Copyright 2024 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.exceptions import AccessError +from odoo.tests.common import Form, TransactionCase, new_test_user + + +class TestProjectCustomerAccess(TransactionCase): + + def setUp(self): + super().setUp() + self.customer = new_test_user( + self.env, + login="test_customer", + groups="project_customer_access.group_customer", + ) + self.manager = new_test_user( + self.env, + login="test_manager", + groups="project_customer_access.group_manager", + ) + + self.form_view = "project_customer_access.view_task_form" + self.project1 = self.env.ref("project.project_project_1") + self.project2 = self.env.ref("project.project_project_2") + self.project_ids = self.project1 | self.project2 + self.task = self.env.ref("project.project_1_task_1") + self.tag = self.env.ref("project.project_tags_00") + self.stage = self.env.ref("project.project_stage_3") + + self.task_customer = self.env["project.task"].create( + { + "name": "Test", + "project_id": self.project1.id, + "user_ids": [Command.link(self.customer.id)], + } + ) + + # Cross connection made by other module + self.endpoint = self.env["fastapi.endpoint"].create( + { + "name": "Cross Connect Server Endpoint", + "root_path": "/api", + "app": "cross_connect", + } + ) + self.client = self.env["cross.connect.client"].create( + { + "name": "Test Client", + "endpoint_id": self.endpoint.id, + "api_key": "server-api-key", + } + ) + (self.customer | self.manager).write( + {"cross_connect_client_id": self.client.id} + ) + self.project_ids.write({"cross_connect_client_id": self.client.id}) + + def test_visible_projects(self): + customer_proj = self.env["project.project"].with_user(self.customer).search([]) + manager_proj = self.env["project.project"].with_user(self.manager).search([]) + + self.assertEqual(customer_proj, self.project_ids) + self.assertEqual(manager_proj, self.project_ids) + + def test_visible_tasks(self): + task_ids = self.env["project.task"].search( + [("project_id", "in", self.project_ids.ids)] + ) + customer_tasks = self.env["project.task"].with_user(self.customer).search([]) + manager_tasks = self.env["project.task"].with_user(self.manager).search([]) + + self.assertEqual(customer_tasks, task_ids) + self.assertEqual(manager_tasks, task_ids) + + def test_edit_own_task_customer(self): + task_id = self.task_customer.with_user(self.customer) + with Form(task_id, view=self.form_view) as f: + f.name = "Test" + f.description = "Test" + with self.assertRaisesRegex(AssertionError, "can't write on readonly"): + f.project_id = self.project2 + + self.assertEqual(task_id.name, "Test") + self.assertIn("Test", str(task_id.description)) + + def test_edit_other_task_customer(self): + task_id = self.task.with_user(self.customer) + task_form = Form(task_id, view=self.form_view) + with self.assertRaisesRegex(AssertionError, "can't write on readonly"): + task_form.name = "Test" + + def test_edit_task_manager(self): + task_id = self.task.with_user(self.manager) + with Form(task_id, view=self.form_view) as f: + f.name = "Test" + f.description = "Test" + f.tag_ids.add(self.tag) + f.stage_id = self.stage + f.project_id = self.project2 + + self.assertEqual(task_id.name, "Test") + self.assertIn("Test", str(task_id.description)) + self.assertEqual(task_id.tag_ids, self.tag) + self.assertEqual(task_id.stage_id, self.stage) + self.assertEqual(task_id.project_id, self.project2) + + def test_no_unlink_manager(self): + with self.assertRaisesRegex(AccessError, "You are not allowed to delete"): + self.task_customer.with_user(self.manager).unlink() + + def test_create_task_customer(self): + f = Form(self.env["project.task"].with_user(self.customer), view=self.form_view) + f.name = "New" + f.description = "New" + f.project_id = self.project1 + task_id = f.save() + + self.assertEqual(task_id.name, "New") + self.assertIn("New", str(task_id.description)) + self.assertEqual(task_id.project_id, self.project1) + + def test_create_task_customer_no_project(self): + f = Form(self.env["project.task"].with_user(self.customer), view=self.form_view) + f.name = "New" + with self.assertRaisesRegex(AssertionError, "project_id is a required field"): + task_id = f.save() diff --git a/project_customer_access/views/project_task_views.xml b/project_customer_access/views/project_task_views.xml new file mode 100644 index 0000000..42af2c4 --- /dev/null +++ b/project_customer_access/views/project_task_views.xml @@ -0,0 +1,310 @@ + + + + + + + + + project.task.search.form (in project_customer_access) + project.task + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project.task.form (in project_customer_access) + project.task + + +
+ + + + +
+ +
+ + +
+

+
+ + +
+ +

+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+
+ + + project.task.kanban (in project_customer_access) + project.task + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ + + + + + + + + +
+ +
+
+ +
+ +
+
+ +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
+
+
+
+ + + project.task.tree (in project_customer_access) + project.task + + + + + + + + + + + + + + + + + + + + + + + + + + My Tasks + project.task + kanban,tree,form + {'search_default_my_tasks': 1, 'search_default_open_tasks': 1, 'all_task': 0, 'default_user_ids': [(4, uid)]} + + +

+ No tasks found. Let's create one! +

+

+ Keep track of the progress of your tasks from creation to completion.
+ Collaborate efficiently by chatting in real-time or via email. +

+
+
+ + + + kanban + + + + + + tree + + + + + + form + + + + + + + + + project.project.form in project_customer_access + project.project + + + + + + + + + + + res.users.form in project_customer_access + res.users + + + + + + + + +
diff --git a/setup/project_customer_access/odoo/addons/project_customer_access b/setup/project_customer_access/odoo/addons/project_customer_access new file mode 120000 index 0000000..a001215 --- /dev/null +++ b/setup/project_customer_access/odoo/addons/project_customer_access @@ -0,0 +1 @@ +../../../../project_customer_access \ No newline at end of file diff --git a/setup/project_customer_access/setup.py b/setup/project_customer_access/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/project_customer_access/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 2862578766fe5d1e7d04a583b4c1231ba2c0ae92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Mombereau?= Date: Tue, 24 Dec 2024 15:16:41 -0300 Subject: [PATCH 2/7] [IMP] add origin readonly fields --- project_customer_access/models/project.py | 4 ++++ project_customer_access/views/project_task_views.xml | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/project_customer_access/models/project.py b/project_customer_access/models/project.py index a133ff9..4317048 100644 --- a/project_customer_access/models/project.py +++ b/project_customer_access/models/project.py @@ -16,6 +16,10 @@ class ProjectProject(models.Model): class ProjectTask(models.Model): _inherit = "project.task" + origin_url = fields.Char() + origin_db = fields.Char() + origin_name = fields.Char() + def _get_customer_access_view_ids(self): form_id = self.env.ref("project_customer_access.view_task_form") kanban_id = self.env.ref("project_customer_access.view_task_kanban") diff --git a/project_customer_access/views/project_task_views.xml b/project_customer_access/views/project_task_views.xml index 42af2c4..4fed2e6 100644 --- a/project_customer_access/views/project_task_views.xml +++ b/project_customer_access/views/project_task_views.xml @@ -114,6 +114,13 @@ + + + + + + +
From c142c6757cd04b989843560b5024998cc69fa901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Mon, 17 Mar 2025 14:36:00 +0100 Subject: [PATCH 3/7] project_customer_access: fix access right, update default filter, rename menu, fix typo --- project_customer_access/data/res_groups.xml | 8 ++--- .../security/ir.model.access.csv | 3 +- .../views/project_task_views.xml | 30 +++++++++---------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/project_customer_access/data/res_groups.xml b/project_customer_access/data/res_groups.xml index 689a58d..5de5a3d 100644 --- a/project_customer_access/data/res_groups.xml +++ b/project_customer_access/data/res_groups.xml @@ -5,14 +5,14 @@ 90 - User + Support User - Manager + Support Manager - + - \ No newline at end of file + diff --git a/project_customer_access/security/ir.model.access.csv b/project_customer_access/security/ir.model.access.csv index ee1f250..23650ea 100644 --- a/project_customer_access/security/ir.model.access.csv +++ b/project_customer_access/security/ir.model.access.csv @@ -3,4 +3,5 @@ access_customer_project,access_customer_project,project.model_project_project,pr access_customer_task,access_customer_task,project.model_project_task,project_customer_access.group_customer,1,1,1,0 access_customer_task_type,access_customer_task_type,project.model_project_task_type,project_customer_access.group_customer,1,0,0,0 access_customer_internal_task_type,access_customer_internal_task_type,project.model_project_task_stage_personal,project_customer_access.group_customer,1,1,0,0 -access_customer_task_rating,access_customer_task_rating,rating.model_rating_rating,project_customer_access.group_customer,1,0,0,0 \ No newline at end of file +access_customer_task_rating,access_customer_task_rating,rating.model_rating_rating,project_customer_access.group_customer,1,0,0,0 +access_customer_date_range,access_customer_date_range,date_range.model_date_range,project_customer_access.group_customer,1,0,0,0 diff --git a/project_customer_access/views/project_task_views.xml b/project_customer_access/views/project_task_views.xml index 4fed2e6..3237d54 100644 --- a/project_customer_access/views/project_task_views.xml +++ b/project_customer_access/views/project_task_views.xml @@ -53,7 +53,7 @@ - + project.task.form (in project_customer_access) @@ -107,12 +107,12 @@ - + - + @@ -120,7 +120,7 @@ - +
@@ -129,7 +129,7 @@
-
+ project.task.kanban (in project_customer_access) @@ -139,8 +139,8 @@ default_group_by="stage_id" class="o_kanban_small_column o_kanban_project_tasks" examples="project" - js_class="project_task_kanban" - sample="1" + js_class="project_task_kanban" + sample="1" > @@ -247,10 +247,10 @@ - My Tasks + Tasks project.task kanban,tree,form - {'search_default_my_tasks': 1, 'search_default_open_tasks': 1, 'all_task': 0, 'default_user_ids': [(4, uid)]} + {'search_default_open_tasks': 1}

@@ -283,8 +283,8 @@ - - + - + @@ -309,9 +309,9 @@ - + - + From 493704a8a553a7e13bd9db457cd083fc58414e9e Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Wed, 19 Mar 2025 15:45:01 +0100 Subject: [PATCH 4/7] [IMP] project_customer_access: Depend on project_sprint, fix acl and add view fields --- project_customer_access/__manifest__.py | 4 +- .../security/ir.model.access.csv | 1 + .../views/project_task_views.xml | 53 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/project_customer_access/__manifest__.py b/project_customer_access/__manifest__.py index 182f3d3..811360f 100644 --- a/project_customer_access/__manifest__.py +++ b/project_customer_access/__manifest__.py @@ -9,7 +9,9 @@ "author": "Akretion", "website": "http://akretion.com", "depends": [ - "project", + "date_range", + # https://github.com/akretion/ak-odoo-incubator + "project_sprint", # https://github.com/OCA/server-backend "base_group_backend", # https://github.com/OCA/server-auth diff --git a/project_customer_access/security/ir.model.access.csv b/project_customer_access/security/ir.model.access.csv index 23650ea..58061b0 100644 --- a/project_customer_access/security/ir.model.access.csv +++ b/project_customer_access/security/ir.model.access.csv @@ -5,3 +5,4 @@ access_customer_task_type,access_customer_task_type,project.model_project_task_t access_customer_internal_task_type,access_customer_internal_task_type,project.model_project_task_stage_personal,project_customer_access.group_customer,1,1,0,0 access_customer_task_rating,access_customer_task_rating,rating.model_rating_rating,project_customer_access.group_customer,1,0,0,0 access_customer_date_range,access_customer_date_range,date_range.model_date_range,project_customer_access.group_customer,1,0,0,0 +access_customer_sprint,access_customer_sprint,project_sprint.model_project_sprint,project_customer_access.group_customer,1,0,0,0 diff --git a/project_customer_access/views/project_task_views.xml b/project_customer_access/views/project_task_views.xml index 3237d54..59a00b8 100644 --- a/project_customer_access/views/project_task_views.xml +++ b/project_customer_access/views/project_task_views.xml @@ -26,6 +26,27 @@ + + + + + @@ -50,6 +71,27 @@ + + + + + @@ -100,7 +142,11 @@ widget="many2many_avatar_user" domain="[('share', '=', False), ('active', '=', True)]" readonly="1"/> - + + @@ -232,6 +278,11 @@ + + From a42eb0337e3a2ef51e607c4ec23fd051878e6a71 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Wed, 19 Mar 2025 15:45:41 +0100 Subject: [PATCH 5/7] [IMP] project_customer_access: Add projects filters --- project_customer_access/models/project.py | 39 ++++++++++++++++------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/project_customer_access/models/project.py b/project_customer_access/models/project.py index 4317048..44b9c66 100644 --- a/project_customer_access/models/project.py +++ b/project_customer_access/models/project.py @@ -23,7 +23,8 @@ class ProjectTask(models.Model): def _get_customer_access_view_ids(self): form_id = self.env.ref("project_customer_access.view_task_form") kanban_id = self.env.ref("project_customer_access.view_task_kanban") - return form_id | kanban_id + search_id = self.env.ref("project_customer_access.view_task_search_form") + return form_id | kanban_id | search_id def _get_editable_fields_customer(self): return ["name", "description"] @@ -54,18 +55,34 @@ def _get_readonly_value(self, field): def get_view(self, view_id=None, view_type="form", **options): res = super().get_view(view_id=view_id, view_type=view_type, **options) customer_access_view_ids = self._get_customer_access_view_ids() - if view_id in customer_access_view_ids.ids: doc = etree.XML(res["arch"]) - - for field in doc.xpath("//field[@name][not(ancestor::field)]"): - modifiers = json.loads( - field.attrib.get("modifiers", '{"readonly": false}') - ) - if modifiers.get("readonly") is not True: - modifiers["readonly"] = self._get_readonly_value(field) - - field.attrib["modifiers"] = json.dumps(modifiers) + if view_type in ["form", "kanban"]: + for field in doc.xpath("//field[@name][not(ancestor::field)]"): + modifiers = json.loads( + field.attrib.get("modifiers", '{"readonly": false}') + ) + if modifiers.get("readonly") is not True: + modifiers["readonly"] = self._get_readonly_value(field) + + field.attrib["modifiers"] = json.dumps(modifiers) + + # List all accessible projects in filters for customers project users + + if view_type == "search": + projects = self.env["project.project"].search([]) + node = doc.xpath("//search")[0] + idx = node.index(node.xpath("//filter[@name='unassigned']")[0]) + for project in reversed(projects): + elem = etree.Element( + "filter", + string=project.name, + name=f"project_{project.id}", + domain=f"[('project_id', '=', {project.id})]", + ) + node.insert(idx, elem) + if projects: + node.insert(idx, etree.Element("separator")) res["arch"] = etree.tostring(doc, pretty_print=True) From 494823a19a8178cfdae8089ca9f6c393182d13d1 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Thu, 20 Mar 2025 12:27:49 +0100 Subject: [PATCH 6/7] [IMP] project_customer_access: Force save auto filled fields --- project_customer_access/views/project_task_views.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project_customer_access/views/project_task_views.xml b/project_customer_access/views/project_task_views.xml index 59a00b8..8813037 100644 --- a/project_customer_access/views/project_task_views.xml +++ b/project_customer_access/views/project_task_views.xml @@ -162,9 +162,9 @@ - - - + + + From 3e4f8aa994320552653ef0b30e0dd41e46d090bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt?= Date: Thu, 3 Apr 2025 19:03:58 +0200 Subject: [PATCH 7/7] allow customer to select users on tasks and change files place --- project_customer_access/__manifest__.py | 4 +-- .../project_customer_access_demo.xml | 0 project_customer_access/models/project.py | 4 ++- .../project_customer_access_security.xml | 15 ++++++++- .../{data => security}/res_groups.xml | 0 .../views/project_task_views.xml | 32 +++++++++++++------ 6 files changed, 41 insertions(+), 14 deletions(-) rename project_customer_access/{data => demo}/project_customer_access_demo.xml (100%) rename project_customer_access/{data => security}/res_groups.xml (100%) diff --git a/project_customer_access/__manifest__.py b/project_customer_access/__manifest__.py index 811360f..3b06b6b 100644 --- a/project_customer_access/__manifest__.py +++ b/project_customer_access/__manifest__.py @@ -18,10 +18,10 @@ "cross_connect_server", ], "data": [ - "data/res_groups.xml", + "security/res_groups.xml", "security/ir.model.access.csv", "security/project_customer_access_security.xml", "views/project_task_views.xml", ], - "demo": ["data/project_customer_access_demo.xml"], + "demo": ["demo/project_customer_access_demo.xml"], } diff --git a/project_customer_access/data/project_customer_access_demo.xml b/project_customer_access/demo/project_customer_access_demo.xml similarity index 100% rename from project_customer_access/data/project_customer_access_demo.xml rename to project_customer_access/demo/project_customer_access_demo.xml diff --git a/project_customer_access/models/project.py b/project_customer_access/models/project.py index 44b9c66..8d98d65 100644 --- a/project_customer_access/models/project.py +++ b/project_customer_access/models/project.py @@ -19,6 +19,8 @@ class ProjectTask(models.Model): origin_url = fields.Char() origin_db = fields.Char() origin_name = fields.Char() + cross_connect_client_id = fields.Many2one("cross.connect.client", related="project_id.cross_connect_client_id", store=True) + user_ids = fields.Many2many(domain="[('cross_connect_client_id', 'in', [False, cross_connect_client_id])]") def _get_customer_access_view_ids(self): form_id = self.env.ref("project_customer_access.view_task_form") @@ -30,7 +32,7 @@ def _get_editable_fields_customer(self): return ["name", "description"] def _get_editable_fields_manager(self): - return ["name", "description", "tag_ids", "stage_id", "project_id"] + return ["name", "description", "tag_ids", "stage_id", "project_id", "user_ids"] def _get_readonly_value(self, field): field_name = field.attrib.get("name") diff --git a/project_customer_access/security/project_customer_access_security.xml b/project_customer_access/security/project_customer_access_security.xml index 0722835..3107f53 100644 --- a/project_customer_access/security/project_customer_access_security.xml +++ b/project_customer_access/security/project_customer_access_security.xml @@ -11,7 +11,7 @@ - + @@ -24,5 +24,18 @@ + + Project Customer Access: Users + + + [('cross_connect_client_id', 'in', [False, user.cross_connect_client_id.id])] + + + + + + + + diff --git a/project_customer_access/data/res_groups.xml b/project_customer_access/security/res_groups.xml similarity index 100% rename from project_customer_access/data/res_groups.xml rename to project_customer_access/security/res_groups.xml diff --git a/project_customer_access/views/project_task_views.xml b/project_customer_access/views/project_task_views.xml index 8813037..6f3a5f3 100644 --- a/project_customer_access/views/project_task_views.xml +++ b/project_customer_access/views/project_task_views.xml @@ -107,6 +107,7 @@ +

@@ -116,10 +117,10 @@

- +
- +

@@ -134,14 +135,12 @@ placeholder="e.g. Product Launch" context="{'default_project_id': project_id if not parent_id or not display_project_id else display_project_id}" attrs="{'invisible': ['|', ('project_id', '=', False), ('allow_milestones', '=', False)]}" - readonly="1" /> + /> - + @@ -162,9 +161,9 @@ - - - + + + @@ -333,7 +332,6 @@ - + + project.task.form + project.task + + + + + + + + + + +