diff --git a/project_timeline/README.rst b/project_timeline/README.rst index b50da8536a..e145301200 100644 --- a/project_timeline/README.rst +++ b/project_timeline/README.rst @@ -45,7 +45,11 @@ To view the timeline: * Click on the timeline view icon. * You will see the tasks or projects in the new view. -The Task timeline uses the Start Date and End Date fields, in the Extra Info tab. +The Task timeline uses the "Planned Start Date" and "Planned End Date" fields, in the +"Extra Info" tab (only visible in debug mode). + +When a user is assigned, and there's no planned start date, current datetime is filled +there, and the same happens for the end one when the task is put in a done stage. Bug Tracker =========== @@ -79,6 +83,7 @@ Contributors * Pedro M. Baeza * Carlos Dauden * Alexandre Díaz + * Juan José Seguí * `Open Source Integrators `_: diff --git a/project_timeline/__init__.py b/project_timeline/__init__.py index adb73323b1..83e553ac46 100644 --- a/project_timeline/__init__.py +++ b/project_timeline/__init__.py @@ -1,4 +1,3 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from .hooks import populate_date_start from . import models diff --git a/project_timeline/__manifest__.py b/project_timeline/__manifest__.py index 77c3a2ff51..d930bec515 100644 --- a/project_timeline/__manifest__.py +++ b/project_timeline/__manifest__.py @@ -6,7 +6,7 @@ { "name": "Project timeline", "summary": "Timeline view for projects", - "version": "14.0.1.5.0", + "version": "14.0.2.0.0", "category": "Project Management", "website": "https://github.com/OCA/project", "author": "Tecnativa, Onestein, Odoo Community Association (OCA)", @@ -18,5 +18,4 @@ "views/project_task_view.xml", ], "demo": ["demo/project_project_demo.xml", "demo/project_task_demo.xml"], - "post_init_hook": "populate_date_start", } diff --git a/project_timeline/demo/project_task_demo.xml b/project_timeline/demo/project_task_demo.xml index becf2c6765..e837256bfd 100644 --- a/project_timeline/demo/project_task_demo.xml +++ b/project_timeline/demo/project_task_demo.xml @@ -4,191 +4,191 @@ diff --git a/project_timeline/hooks.py b/project_timeline/hooks.py deleted file mode 100644 index 8f289c38c0..0000000000 --- a/project_timeline/hooks.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2021 Open Source Integrators - Daniel Reis -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - - -def populate_date_start(cr, registry): - """ - The date_start was introduced to be used instead of date_assign. - To keep same behaviour on upgrade, initialize it - to have the same data as before. - """ - cr.execute( - "UPDATE project_task " - "SET date_start = date_assign " - "WHERE date_start IS NULL " - "AND date_assign IS NOT NULL" - ) diff --git a/project_timeline/migrations/14.0.2.0.0/post-migration.py b/project_timeline/migrations/14.0.2.0.0/post-migration.py new file mode 100644 index 0000000000..fc7c520fe8 --- /dev/null +++ b/project_timeline/migrations/14.0.2.0.0/post-migration.py @@ -0,0 +1,16 @@ +# Copyright 2024 Tecnativa - Juan José Seguí +# Copyright 2024 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.logged_query( + env.cr, + """UPDATE project_task + SET planned_date_end = date_end + WHERE planned_date_end IS NULL AND date_end IS NOT NULL; + """, + ) diff --git a/project_timeline/migrations/14.0.2.0.0/pre-migration.py b/project_timeline/migrations/14.0.2.0.0/pre-migration.py new file mode 100644 index 0000000000..30154ce114 --- /dev/null +++ b/project_timeline/migrations/14.0.2.0.0/pre-migration.py @@ -0,0 +1,11 @@ +# Copyright 2024 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.rename_fields( + env, [("project.task", "project_task", "date_start", "planned_date_start")] + ) diff --git a/project_timeline/models/project_task.py b/project_timeline/models/project_task.py index 38de489147..84eda6653f 100644 --- a/project_timeline/models/project_task.py +++ b/project_timeline/models/project_task.py @@ -1,17 +1,85 @@ -# Copyright 2016-2017 Tecnativa - Pedro M. Baeza # Copyright 2017 Tecnativa - Carlos Dauden # Copyright 2021 Open Source Integrators - Daniel Reis +# Copyright 2016-2024 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.tools.sql import column_exists, create_column class ProjectTask(models.Model): _inherit = "project.task" - date_start = fields.Datetime("Start Date") + planned_date_start = fields.Datetime( + compute="_compute_planned_date_start", + store=True, + readonly=False, + ) + planned_date_end = fields.Datetime( + compute="_compute_planned_date_end", + store=True, + readonly=False, + ) - def update_date_end(self, stage_id): - res = super().update_date_end(stage_id) - res.pop("date_end", None) - return res + @api.depends("date_assign") + def _compute_planned_date_start(self): + """Put the assignation date as the planned start if not other value is + previously set, avoiding to trigger the constraint. + """ + for record in self.filtered( + lambda x: not x.planned_date_start and x.date_assign + ): + if ( + not record.planned_date_end + or record.planned_date_end >= record.date_assign + ): + record.planned_date_start = record.date_assign + + @api.depends("date_end") + def _compute_planned_date_end(self): + """Put the done date as the planned end if not other value is previously set, + avoiding to trigger the constraint. + """ + for record in self.filtered(lambda x: not x.planned_date_end and x.date_end): + if ( + not record.planned_date_start + or record.planned_date_start <= record.date_end + ): + record.planned_date_end = record.date_end + + @api.constrains("planned_date_start", "planned_date_end") + def _check_planned_dates(self): + for task in self: + if task.planned_date_start and task.planned_date_end: + if task.planned_date_end < task.planned_date_start: + raise ValidationError( + _("The end date must be after the start date.") + ) + + def _auto_init(self): + # Pre-create and fill planned_date_start and planned_date_end columns for + # avoiding a costly computation and possible conflicts with the constraint + cr = self.env.cr + if not column_exists(cr, "project_task", "planned_date_start"): + create_column(cr, "project_task", "planned_date_start", "timestamp") + cr.execute( + """ + UPDATE project_task + SET planned_date_start = date_assign + WHERE planned_date_start IS NULL + AND date_assign IS NOT NULL + """ + ) + if not column_exists(cr, "project_task", "planned_date_end"): + create_column(cr, "project_task", "planned_date_end", "timestamp") + cr.execute( + """ + UPDATE project_task + SET planned_date_end = date_end + WHERE planned_date_end IS NULL + AND date_end IS NOT NULL + AND COALESCE(planned_date_start, date_end) <= date_end + """ + ) + return super()._auto_init() diff --git a/project_timeline/readme/CONTRIBUTORS.rst b/project_timeline/readme/CONTRIBUTORS.rst index b314a14016..b073352c3d 100644 --- a/project_timeline/readme/CONTRIBUTORS.rst +++ b/project_timeline/readme/CONTRIBUTORS.rst @@ -8,6 +8,7 @@ * Pedro M. Baeza * Carlos Dauden * Alexandre Díaz + * Juan José Seguí * `Open Source Integrators `_: diff --git a/project_timeline/readme/USAGE.rst b/project_timeline/readme/USAGE.rst index 1357433890..3c4d48f4f3 100644 --- a/project_timeline/readme/USAGE.rst +++ b/project_timeline/readme/USAGE.rst @@ -4,4 +4,8 @@ To view the timeline: * Click on the timeline view icon. * You will see the tasks or projects in the new view. -The Task timeline uses the Start Date and End Date fields, in the Extra Info tab. +The Task timeline uses the "Planned Start Date" and "Planned End Date" fields, in the +"Extra Info" tab (only visible in debug mode). + +When a user is assigned, and there's no planned start date, current datetime is filled +there, and the same happens for the end one when the task is put in a done stage. diff --git a/project_timeline/static/description/index.html b/project_timeline/static/description/index.html index e84adbc7fd..44bbe2453a 100644 --- a/project_timeline/static/description/index.html +++ b/project_timeline/static/description/index.html @@ -1,4 +1,3 @@ - @@ -393,7 +392,10 @@

Usage

  • Click on the timeline view icon.
  • You will see the tasks or projects in the new view.
  • -

    The Task timeline uses the Start Date and End Date fields, in the Extra Info tab.

    +

    The Task timeline uses the “Planned Start Date” and “Planned End Date” fields, in the +“Extra Info” tab (only visible in debug mode).

    +

    When a user is assigned, and there’s no planned start date, current datetime is filled +there, and the same happens for the end one when the task is put in a done stage.

    Bug Tracker

    @@ -423,6 +425,7 @@

    Contributors

  • Pedro M. Baeza
  • Carlos Dauden
  • Alexandre Díaz
  • +
  • Juan José Seguí
  • Open Source Integrators:
      diff --git a/project_timeline/tests/test_project_timeline.py b/project_timeline/tests/test_project_timeline.py index 4478400bc5..11e89790fb 100644 --- a/project_timeline/tests/test_project_timeline.py +++ b/project_timeline/tests/test_project_timeline.py @@ -1,4 +1,5 @@ # Copyright 2018 Onestein +# Copyright 2024 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields @@ -6,16 +7,29 @@ class TestProjectTimeline(TransactionCase): - def test_date_end_doesnt_unset(self): + def test_01_flow_filling(self): + task = self.env["project.task"].create({"name": "1"}) + self.assertFalse(task.planned_date_start) + task.user_id = self.env.user.id + self.assertTrue(task.planned_date_start) + self.assertFalse(task.planned_date_end) + task.stage_id = self.ref("project.project_stage_2") + self.assertTrue(task.planned_date_end) + + def test_02_no_filling(self): stage_id = self.ref("project.project_stage_2") task = self.env["project.task"].create( { "name": "1", - "date_assign": "2018-05-01 00:00:00", - "date_end": "2018-05-07 00:00:00", + "planned_date_start": "2018-05-01 00:00:00", + "planned_date_end": "2018-05-07 00:00:00", } ) - task.write({"stage_id": stage_id, "date_end": "2018-10-07 00:00:00"}) + task.user_id = self.env.user.id + self.assertEqual( + task.planned_date_start, fields.Datetime.from_string("2018-05-01") + ) + task.stage_id = stage_id self.assertEqual( - task.date_end, fields.Datetime.from_string("2018-10-07 00:00:00") + task.planned_date_end, fields.Datetime.from_string("2018-05-07") ) diff --git a/project_timeline/views/project_task_view.xml b/project_timeline/views/project_task_view.xml index 23f1508b3a..55b9f7871d 100644 --- a/project_timeline/views/project_task_view.xml +++ b/project_timeline/views/project_task_view.xml @@ -11,8 +11,8 @@ timeline timeline @@ -111,8 +111,28 @@ - - +