diff --git a/README.md b/README.md index e03b28db4d..f260637a76 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ addon | version | maintainers | summary [project_task_code](project_task_code/) | 18.0.1.0.1 | | Sequential Code for Tasks [project_task_code_portal](project_task_code_portal/) | 18.0.1.1.1 | | Use custom task code in customer portal [project_task_default_stage](project_task_default_stage/) | 18.0.1.0.1 | | Recovery default task stages for projects from v8 +[project_task_default_user](project_task_default_user/) | 18.0.1.0.0 | NICO-SOLUTIONS | Auto assign default users to tasks or when changing task stages [project_task_description_portal](project_task_description_portal/) | 18.0.1.0.0 | | Dedicated task description for portal users [project_task_description_template](project_task_description_template/) | 18.0.1.0.0 | | Add a description template to project tasks [project_task_material](project_task_material/) | 18.0.1.0.0 | | Record products spent in a Task diff --git a/project_task_default_user/README.rst b/project_task_default_user/README.rst new file mode 100644 index 0000000000..fcbc4f272d --- /dev/null +++ b/project_task_default_user/README.rst @@ -0,0 +1,120 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +========================= +Project Task Default User +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2b00bf26a2b4a420fcca734cb4c09820c5f24d00842a28575c17437c96897c4d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fproject-lightgray.png?logo=github + :target: https://github.com/OCA/project/tree/18.0/project_task_default_user + :alt: OCA/project +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/project-18-0/project-18-0-project_task_default_user + :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/project&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module automatically assigns default users to tasks. + +- Stage-based assignment (priority rule): When creating a task or + changing its stage to one with default users, those users are + automatically assigned according to the stage’s assignment mode + (replace or merge). Stage-based defaults always take priority over + project defaults. +- Project-based fallback: If the stage does not define any default + users, project default users are applied using the same assignment + mode logic (replace or merge). +- Assignment behavior: The assignment mode controls how users are + applied: + + - replace: replaces existing task users with default users + - merge: adds default users to the existing task users without + removing them + +- Multiple default users supported: Both project and stage can define + multiple default users. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To use this module, you need to: + +1. Open Project settings and set default users in the "Default Users" + tab and configure the assignment mode. +2. Activate developer mode. +3. Go to Project → Configuration → Task Stages, set default users in the + "Default Users for this Stage" field and configure the assignment + mode. + +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 +------- + +* NICO SOLUTIONS - ENGINEERING & IT + +Contributors +------------ + +- `NICO SOLUTIONS - ENGINEERING & IT `__: + + - Nils Coenen + +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-NICO-SOLUTIONS| image:: https://github.com/NICO-SOLUTIONS.png?size=40px + :target: https://github.com/NICO-SOLUTIONS + :alt: NICO-SOLUTIONS + +Current `maintainer `__: + +|maintainer-NICO-SOLUTIONS| + +This module is part of the `OCA/project `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/project_task_default_user/__init__.py b/project_task_default_user/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/project_task_default_user/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/project_task_default_user/__manifest__.py b/project_task_default_user/__manifest__.py new file mode 100644 index 0000000000..4a8d23e3db --- /dev/null +++ b/project_task_default_user/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2025 NICO SOLUTIONS - ENGINEERING & IT +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +{ + "name": "Project Task Default User", + "summary": "Auto assign default users to tasks or when changing task stages", + "version": "18.0.1.0.0", + "category": "Project", + "author": "NICO SOLUTIONS - ENGINEERING & IT, Odoo Community Association (OCA)", + "maintainers": ["NICO-SOLUTIONS"], + "website": "https://github.com/OCA/project", + "depends": ["project"], + "data": [ + "views/project_project_views.xml", + "views/project_task_type_views.xml", + ], + "license": "AGPL-3", + "installable": True, + "application": False, + "auto_install": False, +} diff --git a/project_task_default_user/i18n/project_task_default_user.pot b/project_task_default_user/i18n/project_task_default_user.pot new file mode 100644 index 0000000000..34eecb141e --- /dev/null +++ b/project_task_default_user/i18n/project_task_default_user.pot @@ -0,0 +1,86 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_task_default_user +# +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: project_task_default_user +#: model:ir.model.fields,field_description:project_task_default_user.field_project_project__default_user_ids +msgid "Default Users for Tasks" +msgstr "" + +#. module: project_task_default_user +#: model:ir.model.fields,field_description:project_task_default_user.field_project_task_type__default_user_ids +msgid "Default Users for this Stage" +msgstr "" + +#. module: project_task_default_user +#: model_terms:ir.ui.view,arch_db:project_task_default_user.project_edit_project_inherit_default_user +msgid "Default users" +msgstr "" + +#. module: project_task_default_user +#: model:ir.model.fields,help:project_task_default_user.field_project_project__project_task_assignment_mode +#: model:ir.model.fields,help:project_task_default_user.field_project_task_type__stage_task_assignment_mode +msgid "" +"Defines how default users are applied:\n" +"- Replace: overwrite existing users\n" +"- Merge: add default users to existing ones\n" +"\n" +"Applied on task creation and stage changes." +msgstr "" + +#. module: project_task_default_user +#: model:ir.model.fields,help:project_task_default_user.field_project_project__default_user_ids +#: model:ir.model.fields,help:project_task_default_user.field_project_task_type__default_user_ids +msgid "" +"If set, tasks will automatically be assigned to these users. On new tasks, " +"this applies if no users are set. On stage change, the users are replaced " +"only if the new stage has default users." +msgstr "" + +#. module: project_task_default_user +#: model:ir.model.fields.selection,name:project_task_default_user.selection__project_project__project_task_assignment_mode__merge +#: model:ir.model.fields.selection,name:project_task_default_user.selection__project_task_type__stage_task_assignment_mode__merge +msgid "Merge" +msgstr "" + +#. module: project_task_default_user +#: model:ir.model,name:project_task_default_user.model_project_project +msgid "Project" +msgstr "" + +#. module: project_task_default_user +#: model:ir.model.fields,field_description:project_task_default_user.field_project_project__project_task_assignment_mode +msgid "Project Task Assignment Mode" +msgstr "" + +#. module: project_task_default_user +#: model:ir.model.fields.selection,name:project_task_default_user.selection__project_project__project_task_assignment_mode__replace +#: model:ir.model.fields.selection,name:project_task_default_user.selection__project_task_type__stage_task_assignment_mode__replace +msgid "Replace" +msgstr "" + +#. module: project_task_default_user +#: model:ir.model.fields,field_description:project_task_default_user.field_project_task_type__stage_task_assignment_mode +msgid "Stage Task Assignment Mode" +msgstr "" + +#. module: project_task_default_user +#: model:ir.model,name:project_task_default_user.model_project_task +msgid "Task" +msgstr "" + +#. module: project_task_default_user +#: model:ir.model,name:project_task_default_user.model_project_task_type +msgid "Task Stage" +msgstr "" diff --git a/project_task_default_user/models/__init__.py b/project_task_default_user/models/__init__.py new file mode 100644 index 0000000000..4bc8de934a --- /dev/null +++ b/project_task_default_user/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import project_project +from . import project_task_type +from . import project_task diff --git a/project_task_default_user/models/project_project.py b/project_task_default_user/models/project_project.py new file mode 100644 index 0000000000..e755260d23 --- /dev/null +++ b/project_task_default_user/models/project_project.py @@ -0,0 +1,33 @@ +# Copyright 2025 NICO SOLUTIONS - ENGINEERING & IT +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class ProjectProject(models.Model): + _inherit = "project.project" + + default_user_ids = fields.Many2many( + "res.users", + string="Default Users for Tasks", + domain=lambda self: [ + ("groups_id", "in", [self.env.ref("project.group_project_user").id]) + ], + help="If set, tasks will automatically be assigned to these users. " + "On new tasks, this applies if no users are set. On stage change, " + "the users are replaced only if the new stage has default users.", + ) + project_task_assignment_mode = fields.Selection( + [ + ("replace", "Replace"), + ("merge", "Merge"), + ], + default="replace", + help=( + "Defines how default users are applied:\n" + "- Replace: overwrite existing users\n" + "- Merge: add default users to existing ones\n" + "\n" + "Applied on task creation and stage changes." + ), + ) diff --git a/project_task_default_user/models/project_task.py b/project_task_default_user/models/project_task.py new file mode 100644 index 0000000000..35bdec5e23 --- /dev/null +++ b/project_task_default_user/models/project_task.py @@ -0,0 +1,53 @@ +# Copyright 2025 NICO SOLUTIONS - ENGINEERING & IT +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import Command, api, models + + +class ProjectTask(models.Model): + _inherit = "project.task" + + @api.model_create_multi + def create(self, vals_list): + tasks = super().create(vals_list) + for task in tasks: + default_users, mode = task._get_default_users_and_mode() + if default_users: + task._apply_default_users(default_users, mode) + + return tasks + + def write(self, vals): + res = super().write(vals) + if "stage_id" in vals: + for task in self: + default_users, mode = task._get_default_users_and_mode() + if default_users: + task._apply_default_users(default_users, mode) + + return res + + def _get_default_users_and_mode(self): + self.ensure_one() + if self.stage_id and self.stage_id.default_user_ids: + return ( + self.stage_id.default_user_ids.ids, + self.stage_id.stage_task_assignment_mode or "replace", + ) + + if self.project_id and self.project_id.default_user_ids: + return ( + self.project_id.default_user_ids.ids, + self.project_id.project_task_assignment_mode or "replace", + ) + + return None, "replace" + + def _apply_default_users(self, default_users, mode): + self.ensure_one() + if mode == "merge": + existing = self.user_ids.ids or [] + merged = list(dict.fromkeys(existing + default_users)) + self.user_ids = [Command.set(merged)] + else: + self.user_ids = [Command.set(default_users)] diff --git a/project_task_default_user/models/project_task_type.py b/project_task_default_user/models/project_task_type.py new file mode 100644 index 0000000000..20c263eb3b --- /dev/null +++ b/project_task_default_user/models/project_task_type.py @@ -0,0 +1,33 @@ +# Copyright 2025 NICO SOLUTIONS - ENGINEERING & IT +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class ProjectTaskType(models.Model): + _inherit = "project.task.type" + + default_user_ids = fields.Many2many( + "res.users", + string="Default Users for this Stage", + domain=lambda self: [ + ("groups_id", "in", [self.env.ref("project.group_project_user").id]) + ], + help="If set, tasks will automatically be assigned to these users. " + "On new tasks, this applies if no users are set. On stage change, " + "the users are replaced only if the new stage has default users.", + ) + stage_task_assignment_mode = fields.Selection( + [ + ("replace", "Replace"), + ("merge", "Merge"), + ], + default="replace", + help=( + "Defines how default users are applied:\n" + "- Replace: overwrite existing users\n" + "- Merge: add default users to existing ones\n" + "\n" + "Applied on task creation and stage changes." + ), + ) diff --git a/project_task_default_user/pyproject.toml b/project_task_default_user/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/project_task_default_user/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/project_task_default_user/readme/CONFIGURE.md b/project_task_default_user/readme/CONFIGURE.md new file mode 100644 index 0000000000..644cd0798d --- /dev/null +++ b/project_task_default_user/readme/CONFIGURE.md @@ -0,0 +1,5 @@ +To use this module, you need to: + +1. Open Project settings and set default users in the "Default Users" tab and configure the assignment mode. +2. Activate developer mode. +3. Go to Project → Configuration → Task Stages, set default users in the "Default Users for this Stage" field and configure the assignment mode. diff --git a/project_task_default_user/readme/CONTRIBUTORS.md b/project_task_default_user/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..da8a8c2abc --- /dev/null +++ b/project_task_default_user/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [NICO SOLUTIONS - ENGINEERING & IT](https://www.nico-solutions.de): + - Nils Coenen \<\> diff --git a/project_task_default_user/readme/DESCRIPTION.md b/project_task_default_user/readme/DESCRIPTION.md new file mode 100644 index 0000000000..1bbe408c94 --- /dev/null +++ b/project_task_default_user/readme/DESCRIPTION.md @@ -0,0 +1,7 @@ +This module automatically assigns default users to tasks. +- Stage-based assignment (priority rule): When creating a task or changing its stage to one with default users, those users are automatically assigned according to the stage’s assignment mode (replace or merge). Stage-based defaults always take priority over project defaults. +- Project-based fallback: If the stage does not define any default users, project default users are applied using the same assignment mode logic (replace or merge). +- Assignment behavior: The assignment mode controls how users are applied: + - replace: replaces existing task users with default users + - merge: adds default users to the existing task users without removing them +- Multiple default users supported: Both project and stage can define multiple default users. diff --git a/project_task_default_user/static/description/icon.png b/project_task_default_user/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/project_task_default_user/static/description/icon.png differ diff --git a/project_task_default_user/static/description/index.html b/project_task_default_user/static/description/index.html new file mode 100644 index 0000000000..9594f03c78 --- /dev/null +++ b/project_task_default_user/static/description/index.html @@ -0,0 +1,466 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Project Task Default User

+ +

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

+

This module automatically assigns default users to tasks.

+
    +
  • Stage-based assignment (priority rule): When creating a task or +changing its stage to one with default users, those users are +automatically assigned according to the stage’s assignment mode +(replace or merge). Stage-based defaults always take priority over +project defaults.
  • +
  • Project-based fallback: If the stage does not define any default +users, project default users are applied using the same assignment +mode logic (replace or merge).
  • +
  • Assignment behavior: The assignment mode controls how users are +applied:
      +
    • replace: replaces existing task users with default users
    • +
    • merge: adds default users to the existing task users without +removing them
    • +
    +
  • +
  • Multiple default users supported: Both project and stage can define +multiple default users.
  • +
+

Table of contents

+ +
+

Configuration

+

To use this module, you need to:

+
    +
  1. Open Project settings and set default users in the “Default Users” +tab and configure the assignment mode.
  2. +
  3. Activate developer mode.
  4. +
  5. Go to Project → Configuration → Task Stages, set default users in the +“Default Users for this Stage” field and configure the assignment +mode.
  6. +
+
+
+

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

+
    +
  • NICO SOLUTIONS - ENGINEERING & IT
  • +
+
+ +
+

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:

+

NICO-SOLUTIONS

+

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

+

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

+
+
+
+
+ + diff --git a/project_task_default_user/tests/__init__.py b/project_task_default_user/tests/__init__.py new file mode 100644 index 0000000000..2cfda9c7a2 --- /dev/null +++ b/project_task_default_user/tests/__init__.py @@ -0,0 +1 @@ +from . import test_project_task_default_user diff --git a/project_task_default_user/tests/test_project_task_default_user.py b/project_task_default_user/tests/test_project_task_default_user.py new file mode 100644 index 0000000000..f67296e05e --- /dev/null +++ b/project_task_default_user/tests/test_project_task_default_user.py @@ -0,0 +1,129 @@ +from odoo.tests.common import TransactionCase + + +class TestProjectTaskDefaultUser(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + env = cls.env + + cls.user1 = env["res.users"].create( + {"name": "User 1", "login": "user1", "email": "user1@example.com"} + ) + cls.user2 = env["res.users"].create( + {"name": "User 2", "login": "user2", "email": "user2@example.com"} + ) + + cls.project_with_default = env["project.project"].create( + { + "name": "Project with Default", + "default_user_ids": [(6, 0, [cls.user1.id])], + } + ) + + cls.project_without_default = env["project.project"].create( + { + "name": "Project without Default", + } + ) + + cls.stage_with_default = env["project.task.type"].create( + { + "name": "Stage with Default", + "default_user_ids": [(6, 0, [cls.user2.id])], + "project_ids": [(6, 0, [cls.project_with_default.id])], + "stage_task_assignment_mode": "replace", + } + ) + + cls.stage_without_default = env["project.task.type"].create( + { + "name": "Stage without Default", + "project_ids": [(6, 0, [cls.project_with_default.id])], + "stage_task_assignment_mode": "replace", + } + ) + + def test_create_task_stage_default(self): + task = self.env["project.task"].create( + { + "name": "Task Stage Default", + "project_id": self.project_with_default.id, + "stage_id": self.stage_with_default.id, + } + ) + self.assertEqual(task.user_ids.ids, [self.user2.id]) + + def test_create_task_project_default(self): + task = self.env["project.task"].create( + { + "name": "Task Project Default", + "project_id": self.project_with_default.id, + "stage_id": self.stage_without_default.id, + } + ) + self.assertEqual(task.user_ids.ids, [self.user1.id]) + + def test_create_task_no_default(self): + task = self.env["project.task"].create( + { + "name": "Task No Default", + "project_id": self.project_without_default.id, + "stage_id": self.stage_without_default.id, + } + ) + self.assertFalse(set(task.user_ids.ids) & {self.user1.id, self.user2.id}) + + def test_stage_change_with_default(self): + task = self.env["project.task"].create( + { + "name": "Task Change Stage", + "project_id": self.project_with_default.id, + "stage_id": self.stage_without_default.id, + } + ) + task.stage_id = self.stage_with_default + self.assertEqual(task.user_ids.ids, [self.user2.id]) + + def test_stage_change_no_default(self): + task = self.env["project.task"].create( + { + "name": "Task Change Stage", + "project_id": self.project_with_default.id, + "stage_id": self.stage_with_default.id, + } + ) + task.stage_id = self.stage_without_default + self.assertEqual(task.user_ids.ids, [self.user1.id]) + + def test_stage_merge_with_existing_user(self): + self.stage_with_default.write( + { + "stage_task_assignment_mode": "merge", + } + ) + task = self.env["project.task"].create( + { + "name": "Merge With Existing", + "user_ids": [(6, 0, [self.user1.id])], + "project_id": self.project_without_default.id, + "stage_id": self.stage_with_default.id, + } + ) + self.assertEqual(set(task.user_ids.ids), {self.user1.id, self.user2.id}) + + def test_stage_merge_without_existing_user(self): + self.stage_with_default.write( + { + "stage_task_assignment_mode": "merge", + } + ) + task = self.env["project.task"].create( + { + "name": "Empty Merge", + "user_ids": False, + "project_id": self.project_without_default.id, + "stage_id": self.stage_with_default.id, + } + ) + self.assertEqual(set(task.user_ids.ids), {self.user2.id}) diff --git a/project_task_default_user/views/project_project_views.xml b/project_task_default_user/views/project_project_views.xml new file mode 100644 index 0000000000..47c0e392e5 --- /dev/null +++ b/project_task_default_user/views/project_project_views.xml @@ -0,0 +1,24 @@ + + + + + project.edit.project.inherit.default.user + project.project + + + + + + + + + + + + + + + diff --git a/project_task_default_user/views/project_task_type_views.xml b/project_task_default_user/views/project_task_type_views.xml new file mode 100644 index 0000000000..3c778bb5ae --- /dev/null +++ b/project_task_default_user/views/project_task_type_views.xml @@ -0,0 +1,29 @@ + + + + + project.task.type.tree.inherit.default.user + project.task.type + + + + + + + + + + project.task.type.edit.inherit.default.user + project.task.type + + + + + + + + + diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index 3b3ea9bcb4..f76e5cc1c9 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-project" -version = "18.0.20260410.0" +version = "18.0.20260504.0" dependencies = [ "odoo-addon-project_administrator_restricted_visibility==18.0.*", "odoo-addon-project_budget==18.0.*", @@ -30,6 +30,7 @@ dependencies = [ "odoo-addon-project_task_code==18.0.*", "odoo-addon-project_task_code_portal==18.0.*", "odoo-addon-project_task_default_stage==18.0.*", + "odoo-addon-project_task_default_user==18.0.*", "odoo-addon-project_task_description_portal==18.0.*", "odoo-addon-project_task_description_template==18.0.*", "odoo-addon-project_task_material==18.0.*",