diff --git a/project_state_extend/README.rst b/project_state_extend/README.rst new file mode 100644 index 0000000000..c7de118eac --- /dev/null +++ b/project_state_extend/README.rst @@ -0,0 +1,126 @@ +==================== +Project State Extend +==================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:placeholder + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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/16.0/project_state_extend + :alt: OCA/project +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/project-16-0/project-16-0-project_state_extend + :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=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends Odoo's project state selection widget to allow adding extended state options beyond the standard ones (On Track, At Risk, Off Track, On Hold). + +Custom states can be configured with: + +* Display name (translatable) +* Technical name (unique identifier) +* Color (0-11 for status bubble display) +* Sequence (ordering in selection lists) + +Custom states are available for both Projects through the same state selection widget used by native Odoo. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure custom project states: + +#. Go to **Project > Configuration > Extended States** +#. Click **Create** +#. Fill in the form: + + * **Name**: Display name for the state (e.g., "Pending Review") + * **Technical Name**: Unique identifier (lowercase, no spaces, e.g., "pending_review") + * **Color**: Select a color index from 0 to 11 for the status bubble + * **Sequence**: Lower numbers appear first in selection lists + * **Active**: Uncheck to archive the state + +#. Click **Save** + +The extended state will now be available in the state selection widget for Projects. + +**Note**: Technical names must be unique across all extended states. + +Usage +===== + +After configuring extended states, you can use them in Projects and Tasks: + +**For Projects:** + +#. Open a project +#. Below the standard "Status" field, you'll see the "Custom State" field +#. Click to select from available extended states +#. The selection will save automatically + +The extended state field uses the same widget as the native status field, providing a consistent user experience with colored status bubbles. + +**Kanban View:** + +Custom states are also visible in Kanban views for both Projects, with the configured color displayed as a status bubble. + +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 +~~~~~~~ + +* Escodoo + +Contributors +~~~~~~~~~~~~ + +* Escodoo (https://www.escodoo.com.br): + + * Kaynnan Lemes + +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. + +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_state_extend/__init__.py b/project_state_extend/__init__.py new file mode 100644 index 0000000000..31660d6a96 --- /dev/null +++ b/project_state_extend/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/project_state_extend/__manifest__.py b/project_state_extend/__manifest__.py new file mode 100644 index 0000000000..281a7813e2 --- /dev/null +++ b/project_state_extend/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2025 - TODAY, Escodoo. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Project State Extend", + "summary": """ + Extend project status selection widget with custom states""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Escodoo, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/project", + "depends": [ + "project", + ], + "data": [ + "security/ir.model.access.csv", + "views/project_state_extend_views.xml", + ], + "assets": { + "web.assets_backend": [ + "project_state_extend/static/src/**/*.esm.js", + ], + }, + "demo": [ + "demo/project_state_extend_demo.xml", + ], + "installable": True, +} diff --git a/project_state_extend/demo/project_state_extend_demo.xml b/project_state_extend/demo/project_state_extend_demo.xml new file mode 100644 index 0000000000..4b52238b5f --- /dev/null +++ b/project_state_extend/demo/project_state_extend_demo.xml @@ -0,0 +1,45 @@ + + + + + + Pending Review + pending_review + 5 + + 10 + + + + Blocked + blocked + 1 + + 20 + + + + Waiting Client + waiting_client + 3 + + 30 + + + + Ready to Deploy + ready_deploy + 10 + + 40 + + + + In Testing + in_testing + 4 + + 50 + + diff --git a/project_state_extend/models/__init__.py b/project_state_extend/models/__init__.py new file mode 100644 index 0000000000..3388cfffea --- /dev/null +++ b/project_state_extend/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import project_state_extend +from . import project_update +from . import project_project diff --git a/project_state_extend/models/project_project.py b/project_state_extend/models/project_project.py new file mode 100644 index 0000000000..85a40af565 --- /dev/null +++ b/project_state_extend/models/project_project.py @@ -0,0 +1,66 @@ +# Copyright 2025 - TODAY, Kaynnan Lemes +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProjectProject(models.Model): + _inherit = "project.project" + + @api.model + def _get_extended_status_selection(self): + """Get status selection with custom states added""" + # Native states - hardcoded to avoid issues with field inheritance + # These values come from the original Odoo project module + native_states = [ + ("on_track", "On Track"), + ("at_risk", "At Risk"), + ("off_track", "Off Track"), + ("on_hold", "On Hold"), + ("to_define", "Set Status"), + ] + + # Get custom states + custom_states = self.env["project.state.extend"].search( + [], order="sequence, id" + ) + extended_states = [ + (state.technical_name, state.name) for state in custom_states + ] + + return native_states + extended_states + + # Override the last_update_status field to use dynamic selection + last_update_status = fields.Selection( + selection="_get_extended_status_selection", + default="to_define", + compute="_compute_last_update_status", + store=True, + readonly=False, + required=True, + ) + + @api.depends("last_update_status") + def _compute_last_update_color(self): + """Override to handle custom state colors""" + # Native status colors from Odoo core + # Must be defined here because super() would fail on custom states + native_status_color = { + "on_track": 10, # Green + "at_risk": 2, # Orange + "off_track": 1, # Red + "on_hold": 4, # Blue + "to_define": 0, # Gray + } + + # Build extended color map with custom states + custom_states = self.env["project.state.extend"].search([]) + extended_status_color = native_status_color.copy() + for state in custom_states: + extended_status_color[state.technical_name] = state.color + + # Apply colors to all projects + for project in self: + project.last_update_color = extended_status_color.get( + project.last_update_status, 0 + ) diff --git a/project_state_extend/models/project_state_extend.py b/project_state_extend/models/project_state_extend.py new file mode 100644 index 0000000000..0272282777 --- /dev/null +++ b/project_state_extend/models/project_state_extend.py @@ -0,0 +1,40 @@ +# Copyright 2025 - TODAY, Kaynnan Lemes +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProjectStateExtend(models.Model): + _name = "project.state.extend" + _description = "Extended Project State" + _order = "sequence, name" + + name = fields.Char( + required=True, + translate=True, + ) + technical_name = fields.Char( + required=True, + help="Technical name used internally. Must be unique and lowercase.", + ) + color = fields.Integer( + string="Color Index", + required=True, + default=0, + help="Color index for the status bubble (0-11)", + ) + sequence = fields.Integer( + default=100, + help="Sequence for ordering. Lower values appear first.", + ) + active = fields.Boolean( + default=True, + ) + + _sql_constraints = [ + ( + "technical_name_uniq", + "unique(technical_name)", + "Technical name must be unique!", + ) + ] diff --git a/project_state_extend/models/project_update.py b/project_state_extend/models/project_update.py new file mode 100644 index 0000000000..6781a164ba --- /dev/null +++ b/project_state_extend/models/project_update.py @@ -0,0 +1,59 @@ +# Copyright 2025 - TODAY, Kaynnan Lemes +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProjectUpdate(models.Model): + _inherit = "project.update" + + @api.model + def _get_extended_status_selection(self): + """Get status selection with custom states added""" + # Native states - hardcoded to avoid issues with field inheritance + # These values come from the original Odoo project module + native_states = [ + ("on_track", "On Track"), + ("at_risk", "At Risk"), + ("off_track", "Off Track"), + ("on_hold", "On Hold"), + ] + + # Get custom states + custom_states = self.env["project.state.extend"].search( + [], order="sequence, id" + ) + extended_states = [ + (state.technical_name, state.name) for state in custom_states + ] + + return native_states + extended_states + + # Override the status field to use dynamic selection + status = fields.Selection( + selection="_get_extended_status_selection", + required=True, + tracking=True, + ) + + @api.depends("status") + def _compute_color(self): + """Override to handle custom state colors""" + # Native status colors from Odoo core + # Must be defined here because super() would fail on custom states + native_status_color = { + "on_track": 10, # Green + "at_risk": 2, # Orange + "off_track": 1, # Red + "on_hold": 4, # Blue + } + + # Build extended color map with custom states + custom_states = self.env["project.state.extend"].search([]) + extended_status_color = native_status_color.copy() + for state in custom_states: + extended_status_color[state.technical_name] = state.color + + # Apply colors to all updates + for update in self: + update.color = extended_status_color.get(update.status, 0) diff --git a/project_state_extend/readme/CONFIGURE.rst b/project_state_extend/readme/CONFIGURE.rst new file mode 100644 index 0000000000..7fe0eb2f11 --- /dev/null +++ b/project_state_extend/readme/CONFIGURE.rst @@ -0,0 +1,17 @@ +To configure custom project states: + +#. Go to **Project > Configuration > Extended States** +#. Click **Create** +#. Fill in the form: + + * **Name**: Display name for the state (e.g., "Pending Review") + * **Technical Name**: Unique identifier (lowercase, no spaces, e.g., "pending_review") + * **Color**: Select a color index from 0 to 11 for the status bubble + * **Sequence**: Lower numbers appear first in selection lists + * **Active**: Uncheck to archive the state + +#. Click **Save** + +The extended state will now be available in the state selection widget for Projects. + +**Note**: Technical names must be unique across all extended states. diff --git a/project_state_extend/readme/CONTRIBUTORS.rst b/project_state_extend/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..787c68b4e9 --- /dev/null +++ b/project_state_extend/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Escodoo (https://www.escodoo.com.br): + + * Kaynnan Lemes \ No newline at end of file diff --git a/project_state_extend/readme/DESCRIPTION.rst b/project_state_extend/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..dbf90be5e5 --- /dev/null +++ b/project_state_extend/readme/DESCRIPTION.rst @@ -0,0 +1,10 @@ +This module extends Odoo's project state selection widget to allow adding extended state options beyond the standard ones (On Track, At Risk, Off Track, On Hold). + +Custom states can be configured with: + +* Display name (translatable) +* Technical name (unique identifier) +* Color (0-11 for status bubble display) +* Sequence (ordering in selection lists) + +Custom states are available for both Projects through the same state selection widget used by native Odoo. diff --git a/project_state_extend/readme/USAGE.rst b/project_state_extend/readme/USAGE.rst new file mode 100644 index 0000000000..bd850be3af --- /dev/null +++ b/project_state_extend/readme/USAGE.rst @@ -0,0 +1,14 @@ +After configuring extended states, you can use them in Projects and Tasks: + +**For Projects:** + +#. Open a project +#. Below the standard "Status" field, you'll see the "Custom State" field +#. Click to select from available extended states +#. The selection will save automatically + +The extended state field uses the same widget as the native status field, providing a consistent user experience with colored status bubbles. + +**Kanban View:** + +Custom states are also visible in Kanban views for both Projects, with the configured color displayed as a status bubble. diff --git a/project_state_extend/security/ir.model.access.csv b/project_state_extend/security/ir.model.access.csv new file mode 100644 index 0000000000..9e5902a8e5 --- /dev/null +++ b/project_state_extend/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_project_state_extend_user,project.state.extend.user,model_project_state_extend,base.group_user,1,0,0,0 +access_project_state_extend_manager,project.state.extend.manager,model_project_state_extend,project.group_project_manager,1,1,1,1 diff --git a/project_state_extend/static/description/icon.png b/project_state_extend/static/description/icon.png new file mode 100644 index 0000000000..a7f55c188e Binary files /dev/null and b/project_state_extend/static/description/icon.png differ diff --git a/project_state_extend/static/description/index.html b/project_state_extend/static/description/index.html new file mode 100644 index 0000000000..02977d5a25 --- /dev/null +++ b/project_state_extend/static/description/index.html @@ -0,0 +1,469 @@ + + + + + +Project State Extend + + + +
+

Project State Extend

+ + +

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

+

This module extends Odoo’s project state selection widget to allow adding extended state options beyond the standard ones (On Track, At Risk, Off Track, On Hold).

+

Custom states can be configured with:

+
    +
  • Display name (translatable)
  • +
  • Technical name (unique identifier)
  • +
  • Color (0-11 for status bubble display)
  • +
  • Sequence (ordering in selection lists)
  • +
+

Custom states are available for both Projects through the same state selection widget used by native Odoo.

+

Table of contents

+ +
+

Configuration

+

To configure custom project states:

+
    +
  1. Go to Project > Configuration > Extended States
  2. +
  3. Click Create
  4. +
  5. Fill in the form:
      +
    • Name: Display name for the state (e.g., “Pending Review”)
    • +
    • Technical Name: Unique identifier (lowercase, no spaces, e.g., “pending_review”)
    • +
    • Color: Select a color index from 0 to 11 for the status bubble
    • +
    • Sequence: Lower numbers appear first in selection lists
    • +
    • Active: Uncheck to archive the state
    • +
    +
  6. +
  7. Click Save
  8. +
+

The extended state will now be available in the state selection widget for Projects.

+

Note: Technical names must be unique across all extended states.

+
+
+

Usage

+

After configuring extended states, you can use them in Projects and Tasks:

+

For Projects:

+
    +
  1. Open a project
  2. +
  3. Below the standard “Status” field, you’ll see the “Custom State” field
  4. +
  5. Click to select from available extended states
  6. +
  7. The selection will save automatically
  8. +
+

The extended state field uses the same widget as the native status field, providing a consistent user experience with colored status bubbles.

+

Kanban View:

+

Custom states are also visible in Kanban views for both Projects, with the configured color displayed as a status bubble.

+
+
+

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

+
    +
  • Escodoo
  • +
+
+ +
+

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.

+

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_state_extend/static/src/components/project_state_selection_extend/project_state_selection_extend.esm.js b/project_state_extend/static/src/components/project_state_selection_extend/project_state_selection_extend.esm.js new file mode 100644 index 0000000000..f102f38eff --- /dev/null +++ b/project_state_extend/static/src/components/project_state_selection_extend/project_state_selection_extend.esm.js @@ -0,0 +1,95 @@ +/** @odoo-module **/ + +/* eslint-disable */ +import {ProjectStateSelectionField} from "@project/components/project_state_selection/project_state_selection"; +import {ProjectStatusWithColorSelectionField} from "@project/components/project_status_with_color_selection/project_status_with_color_selection_field"; +import {patch} from "@web/core/utils/patch"; +import {useService} from "@web/core/utils/hooks"; +/* eslint-enable */ + +// Patch for project_state_selection widget (used in kanban and form views) +patch( + ProjectStateSelectionField.prototype, + "project_state_extend.project_state_selection", + { + /** + * Override setup to initialize the service for custom colors + */ + setup() { + this._super(...arguments); + this.projectStateExtend = useService("project_state_extend"); + }, + + /** + * Override to include custom status colors + * + * The options are already provided by the Python Selection field, + * so we don't need to override get options(). We just need to + * provide the colors for custom states. + * + * @param {String} value - The status value + * @returns {String} The CSS class for the status color + */ + statusColor(value) { + // First try native colors + const nativeColor = this._super(value); + if (nativeColor) { + return nativeColor; + } + + // Then try custom colors + if (this.projectStateExtend) { + const customColors = this.projectStateExtend.getCustomStatusColors(); + + if (customColors[value] !== undefined) { + const prefix = + this.colorPrefix || "o_status_bubble mx-0 o_color_bubble_"; + return `${prefix}${customColors[value]}`; + } + } + + return ""; + }, + } +); + +// Patch for status_with_color widget (used in tree/list views) +patch( + ProjectStatusWithColorSelectionField.prototype, + "project_state_extend.status_with_color", + { + /** + * Override setup to initialize the service for custom colors + */ + setup() { + this._super(...arguments); + this.projectStateExtend = useService("project_state_extend"); + }, + + /** + * Override to include custom status colors + * + * @param {String} value - The status value + * @returns {String} The CSS class for the status color + */ + statusColor(value) { + // First try native colors using parent's logic + if (this.colors && this.colors[value] !== undefined) { + return this.colorPrefix + this.colors[value]; + } + + // Then try custom colors + if (this.projectStateExtend) { + const customColors = this.projectStateExtend.getCustomStatusColors(); + + if (customColors[value] !== undefined) { + const prefix = + this.colorPrefix || "o_status_bubble mx-0 o_color_bubble_"; + return `${prefix}${customColors[value]}`; + } + } + + return ""; + }, + } +); diff --git a/project_state_extend/static/src/utils/project_state_extend_utils.esm.js b/project_state_extend/static/src/utils/project_state_extend_utils.esm.js new file mode 100644 index 0000000000..4ae0de7d63 --- /dev/null +++ b/project_state_extend/static/src/utils/project_state_extend_utils.esm.js @@ -0,0 +1,81 @@ +/** @odoo-module **/ + +import {registry} from "@web/core/registry"; + +/** + * Service to manage custom project states + */ +const projectStateCustomService = { + dependencies: ["orm"], + + async start(env, {orm}) { + const customStates = {}; + + /** + * Load custom states from database + */ + async function loadCustomStates() { + const states = await orm.searchRead( + "project.state.extend", + [["active", "=", true]], + ["technical_name", "name", "color", "sequence"], + {order: "sequence ASC"} + ); + + customStates.states = states; + customStates.statusColors = {}; + + // Build status colors mapping + states.forEach((state) => { + customStates.statusColors[state.technical_name] = state.color; + }); + + return customStates; + } + + /** + * Get all custom states (cached) + * + * @returns {Array} Array of custom state objects + */ + function getCustomStates() { + return customStates.states || []; + } + + /** + * Get custom status colors mapping + * + * @returns {Object} Object mapping technical_name to color index + */ + function getCustomStatusColors() { + return customStates.statusColors || {}; + } + + /** + * Get merged status colors (native + custom) + * + * @param {Object} nativeStatusColors - Native status colors object + * @returns {Object} Merged object with native and custom colors + */ + function getMergedStatusColors(nativeStatusColors) { + return { + ...nativeStatusColors, + ...getCustomStatusColors(), + }; + } + + // Initialize on service start + await loadCustomStates(); + + return { + loadCustomStates, + getCustomStates, + getCustomStatusColors, + getMergedStatusColors, + }; + }, +}; + +registry.category("services").add("project_state_extend", projectStateCustomService); + +export {projectStateCustomService}; diff --git a/project_state_extend/tests/__init__.py b/project_state_extend/tests/__init__.py new file mode 100644 index 0000000000..717b6bb418 --- /dev/null +++ b/project_state_extend/tests/__init__.py @@ -0,0 +1 @@ +from . import test_project_state_extend diff --git a/project_state_extend/tests/test_project_state_extend.py b/project_state_extend/tests/test_project_state_extend.py new file mode 100644 index 0000000000..ee9d65f58b --- /dev/null +++ b/project_state_extend/tests/test_project_state_extend.py @@ -0,0 +1,164 @@ +# Copyright 2025 - TODAY, Kaynnan Lemes +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import psycopg2 + +from odoo.tests.common import TransactionCase + + +class TestProjectStateExtend(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ProjectStateExtend = cls.env["project.state.extend"] + cls.Project = cls.env["project.project"] + cls.Task = cls.env["project.task"] + + def test_create_extend_state(self): + """Test creating a custom state""" + state = self.ProjectStateExtend.create( + { + "name": "Test State", + "technical_name": "test_state", + "color": 5, + "sequence": 100, + } + ) + self.assertEqual(state.name, "Test State") + self.assertEqual(state.technical_name, "test_state") + self.assertEqual(state.color, 5) + self.assertTrue(state.active) + + def test_unique_technical_name(self): + """Test that technical_name must be unique""" + # Clean up any existing test records + existing = self.ProjectStateExtend.search( + [("technical_name", "=", "unique_state_test_001")] + ) + existing.unlink() + + # Create first state + state1 = self.ProjectStateExtend.create( + { + "name": "State 1", + "technical_name": "unique_state_test_001", + "color": 1, + } + ) + self.assertTrue(state1.id) + + # Try to create duplicate - should raise IntegrityError + with self.assertRaises(psycopg2.IntegrityError): + with self.env.cr.savepoint(): + self.ProjectStateExtend.create( + { + "name": "State 2", + "technical_name": "unique_state_test_001", + "color": 2, + } + ) + + # Clean up test record + state1.unlink() + + def test_project_custom_state_selection(self): + """Test that custom states appear in project status selection""" + # Create a custom state + self.ProjectStateExtend.create( + { + "name": "Custom Project State", + "technical_name": "custom_project_state", + "color": 10, + } + ) + + # Get extended status selection + project = self.Project.create({"name": "Test Project"}) + selection = project._get_extended_status_selection() + + # Check that custom state is in selection + technical_names = [s[0] for s in selection] + self.assertIn("custom_project_state", technical_names) + + # Test assigning custom state + project.last_update_status = "custom_project_state" + self.assertEqual(project.last_update_status, "custom_project_state") + + def test_project_custom_state_color(self): + """Test that custom state colors are computed correctly""" + # Create custom state with specific color + state = self.ProjectStateExtend.create( + { + "name": "Colored State", + "technical_name": "colored_state_test", + "color": 7, + } + ) + + # Create project with custom state + project = self.Project.create( + { + "name": "Test Project", + "last_update_status": "colored_state_test", + } + ) + + # Check that color is computed correctly + self.assertEqual(project.last_update_color, 7) + + # Change state color and recompute + state.color = 3 + project._compute_last_update_color() + self.assertEqual(project.last_update_color, 3) + + def test_project_update_custom_state(self): + """Test that custom states work in project updates""" + # Create a custom state + self.ProjectStateExtend.create( + { + "name": "Custom Update State", + "technical_name": "custom_update_state", + "color": 5, + } + ) + + # Create project and project update + project = self.Project.create({"name": "Test Project"}) + update = self.env["project.update"].create( + { + "project_id": project.id, + "name": "Test Update", + "status": "custom_update_state", + } + ) + + # Check that custom state is assigned + self.assertEqual(update.status, "custom_update_state") + + # Check that color is computed correctly + self.assertEqual(update.color, 5) + + def test_native_states_preserved(self): + """Test that native Odoo states are still available""" + project = self.Project.create({"name": "Test Project"}) + selection = project._get_extended_status_selection() + + # Check that all native states are present + native_states = ["on_track", "at_risk", "off_track", "on_hold", "to_define"] + technical_names = [s[0] for s in selection] + for native_state in native_states: + self.assertIn(native_state, technical_names) + + def test_extend_state_archive(self): + """Test archiving custom state""" + state = self.ProjectStateExtend.create( + { + "name": "Archived State", + "technical_name": "archived_state_test", + "color": 1, + } + ) + self.assertTrue(state.active) + + state.active = False + self.assertFalse(state.active) diff --git a/project_state_extend/views/project_state_extend_views.xml b/project_state_extend/views/project_state_extend_views.xml new file mode 100644 index 0000000000..80c7026ffa --- /dev/null +++ b/project_state_extend/views/project_state_extend_views.xml @@ -0,0 +1,110 @@ + + + + + + project.state.extend.form + project.state.extend + +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + project.state.extend.tree + project.state.extend + + + + + + + + + + + + + project.state.extend.search + project.state.extend + + + + + + + + + + + + + + Custom Project States + project.state.extend + tree,form + {} + +

+ Create a new Custom Project State +

+

+ Define custom states that can be assigned to projects and tasks, + providing additional status options beyond the standard ones. +

+
+
+ + +
diff --git a/setup/project_state_extend/odoo/addons/project_state_extend b/setup/project_state_extend/odoo/addons/project_state_extend new file mode 120000 index 0000000000..44c5a31b0b --- /dev/null +++ b/setup/project_state_extend/odoo/addons/project_state_extend @@ -0,0 +1 @@ +../../../../project_state_extend \ No newline at end of file diff --git a/setup/project_state_extend/setup.py b/setup/project_state_extend/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/project_state_extend/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)