From b77f60b155bf97a115aa209847dc791677b2e924 Mon Sep 17 00:00:00 2001 From: Vincent Larcin Date: Wed, 11 May 2022 10:00:39 +0000 Subject: [PATCH] [REF] project,*: refactor projects/tasks views and reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *=hr_timesheet,sale_project,sale_timesheet,spreadsheet_dashboard_hr_timesheet The main goal of this PR is to minimize code duplication for the search and list views for tasks and projects. Task search views ----------------- The most important change was to merge the search views used in the task reports with the "normal" corresponding search view. The reporting search view should now be almost identical to the normal one in both project and fsm. The `project.task`/`project.task.user` search views are now following this inheritance diagram: ``` ┌─┐ ┌─┤1├─┐ │ └─┘ │ │ │ ┌▼┐ ┌▼┐ │2│ ┌─┤3├─┐ └─┘ │ └─┘ │ │ │ ┌▼┐ ┌▼┐ ┌──┤4│ │7├──┐ │ └┬┘ └┬┘ │ │ │ │ │ ┌▼┐ ┌▼┐ ┌▼┐ ┌▼┐ │5│ │6│ │8│ │9│ └─┘ └─┘ └─┘ └─┘ ``` 1. `view_task_search_form_base`: Base search view containing all the fields/filters common to project, project sharing and fsm 2. `project_sharing_project_task_view_search`: Search view for project sharing. 3. `view_task_search_form_project_fsm_base`: Base search view containing all the fields/filters common to project and fsm (not project sharing). 4. `view_task_search_form_project_base`: Base search view for project only, with fields/filter common to the normal search view and the one used in reporting. 5. `view_task_search_form`: Search view for project. 6. `view_task_project_user_search`: Search view for the project report. 7. `project_task_view_search_fsm_base`: Base search view for fsm only, with fields/filter common to the normal search view and the one used in reporting. 8. `project_task_view_search_fsm`: Search view for fsm. 9. `report_project_task_user_fsm_view_search`: Search view for the project report. With this organization, we can easily add a field to both fsm and project at once, and even to project sharing at the same time. Since the goal is to keep the reproting search views identical to the normal ones, it'll also add the field to both of them. Task list views --------------- Similarly to the search views, the task list views were also refactored to minimize code duplication, following this inheritance diagram: ``` ┌─┐ ┌─┤1├─┐ │ └─┘ │ │ │ ┌▼┐ ┌▼┐ │2│ ┌─┤3├─┐ └─┘ │ └─┘ │ │ │ ┌▼┐ ┌▼┐ │4│ │5│ └─┘ └─┘ ``` 1. `project_task_view_tree_main_base`: Base list view containing all the fields/filters common to project, project sharing and fsm. 2. `project_sharing_project_task_view_tree`: List view for project sharing. 3. `project_task_view_tree_base`: Base list view containing all the fields/filters common to project and fsm (not project sharing). 4. `view_task_tree2`: List view for project 5. `project_task_view_list_fsm`: List view for fsm. Project views ----------------- The search and list views for projects in fsm now inherit the ones from project, since our goal moving forward will be to keep those as similar as possible. Other changes ----------------- - Merged and removed many filters/fields from the task search views. - Changed `total_hours_spent` and ` subtask_effective_hours` labels in `project.task` model to avoid changing them manually in many views. - Renamed `state` into `kanban_state`, `hours_planned` into `planned_hours` and `hours_effective` into `effective_hours` in `report.project.task.user` to make it consistent with `project.task` (so we can keep the same views for both of those models). - Moved the sale line filed in the "Settings" tab in the project form view (so that the `sale_line_employee_ids` field in "Invoicing" takes the full width). --- Enterprise: https://github.com/odoo/enterprise/pull/34106 Upgrade: https://github.com/odoo/upgrade/pull/4216 Task-2746370 closes odoo/odoo#106015 Related: odoo/upgrade#4216 Related: odoo/enterprise#34106 Signed-off-by: Xavier Bol (xbo) --- addons/hr_timesheet/models/project.py | 4 +- addons/hr_timesheet/report/project_report.py | 20 +- .../report/project_report_view.xml | 29 +- .../timesheet_graph/timesheet_graph_model.js | 2 +- addons/hr_timesheet/tests/test_timesheet.py | 2 +- .../views/project_sharing_views.xml | 30 -- addons/hr_timesheet/views/project_views.xml | 49 +-- addons/project/__manifest__.py | 2 +- addons/project/models/project.py | 26 +- addons/project/report/project_report.py | 61 ++-- .../project/report/project_report_views.xml | 70 +---- .../project/views/project_sharing_views.xml | 68 +---- addons/project/views/project_views.xml | 281 ++++++++++-------- addons/sale_project/__manifest__.py | 1 - addons/sale_project/models/project.py | 7 + .../report/project_report_views.xml | 18 -- .../views/project_sharing_views.xml | 14 - .../sale_project/views/project_task_views.xml | 29 +- addons/sale_project/views/project_views.xml | 8 +- addons/sale_timesheet/report/__init__.py | 1 + .../sale_timesheet/report/project_report.py | 24 ++ .../static/tests/tours/sale_timesheet_tour.js | 12 +- .../views/project_sharing_views.xml | 11 - .../views/project_task_views.xml | 25 +- .../data/files/tasks_dashboard.json | 116 ++++---- 25 files changed, 405 insertions(+), 505 deletions(-) delete mode 100644 addons/sale_project/report/project_report_views.xml create mode 100644 addons/sale_timesheet/report/project_report.py diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py index e2a96e5e5b2..c5fa37badec 100644 --- a/addons/hr_timesheet/models/project.py +++ b/addons/hr_timesheet/models/project.py @@ -244,10 +244,10 @@ class Task(models.Model): remaining_hours = fields.Float("Remaining Hours", compute='_compute_remaining_hours', store=True, readonly=True, help="Number of allocated hours minus the number of hours spent.") remaining_hours_percentage = fields.Float(compute='_compute_remaining_hours_percentage', search='_search_remaining_hours_percentage') effective_hours = fields.Float("Hours Spent", compute='_compute_effective_hours', compute_sudo=True, store=True) - total_hours_spent = fields.Float("Total Hours Spent", compute='_compute_total_hours_spent', store=True, help="Time spent on this task and its sub-tasks (and their own sub-tasks).") + total_hours_spent = fields.Float("Total Hours", compute='_compute_total_hours_spent', store=True, help="Time spent on this task and its sub-tasks (and their own sub-tasks).") progress = fields.Float("Progress", compute='_compute_progress_hours', store=True, group_operator="avg") overtime = fields.Float(compute='_compute_progress_hours', store=True) - subtask_effective_hours = fields.Float("Sub-tasks Hours Spent", compute='_compute_subtask_effective_hours', recursive=True, store=True, help="Time spent on the sub-tasks (and their own sub-tasks) of this task.") + subtask_effective_hours = fields.Float("Hours Spent on Sub-Tasks", compute='_compute_subtask_effective_hours', recursive=True, store=True, help="Time spent on the sub-tasks (and their own sub-tasks) of this task.") timesheet_ids = fields.One2many('account.analytic.line', 'task_id', 'Timesheets') encode_uom_in_days = fields.Boolean(compute='_compute_encode_uom_in_days', default=lambda self: self._uom_in_days()) diff --git a/addons/hr_timesheet/report/project_report.py b/addons/hr_timesheet/report/project_report.py index e59c680d8a8..fec381b94de 100644 --- a/addons/hr_timesheet/report/project_report.py +++ b/addons/hr_timesheet/report/project_report.py @@ -7,30 +7,30 @@ from odoo import fields, models, api class ReportProjectTaskUser(models.Model): _inherit = "report.project.task.user" - hours_planned = fields.Float('Planned Hours', readonly=True) - hours_effective = fields.Float('Effective Hours', readonly=True) + planned_hours = fields.Float('Initially Planned Hours', readonly=True) + effective_hours = fields.Float('Hours Spent', readonly=True) remaining_hours = fields.Float('Remaining Hours', readonly=True) + remaining_hours_percentage = fields.Float('Remaining Hours Percentage', readonly=True) progress = fields.Float('Progress', group_operator='avg', readonly=True) overtime = fields.Float(readonly=True) def _select(self): - select_to_append = """, - (t.effective_hours * 100) / NULLIF(t.planned_hours, 0) as progress, - t.effective_hours as hours_effective, + return super()._select() + """, + CASE WHEN COALESCE(t.planned_hours, 0) = 0 THEN 0.0 ELSE LEAST((t.effective_hours * 100) / t.planned_hours, 100) END as progress, + t.effective_hours, t.planned_hours - t.effective_hours - t.subtask_effective_hours as remaining_hours, - NULLIF(t.planned_hours, 0) as hours_planned, - t.overtime as overtime + CASE WHEN t.planned_hours > 0 THEN t.remaining_hours / t.planned_hours ELSE 0 END as remaining_hours_percentage, + t.planned_hours, + t.overtime """ - return super(ReportProjectTaskUser, self)._select() + select_to_append def _group_by(self): - group_by_append = """, + return super()._group_by() + """, t.effective_hours, t.subtask_effective_hours, t.planned_hours, t.overtime """ - return super(ReportProjectTaskUser, self)._group_by() + group_by_append @api.model def _get_view_cache_key(self, view_id=None, view_type='form', **options): diff --git a/addons/hr_timesheet/report/project_report_view.xml b/addons/hr_timesheet/report/project_report_view.xml index d48ebee50d9..6e0f0796d7e 100644 --- a/addons/hr_timesheet/report/project_report_view.xml +++ b/addons/hr_timesheet/report/project_report_view.xml @@ -10,8 +10,8 @@ hr_timesheet_graphview - - + + @@ -23,39 +23,20 @@ - - + + - - report.project.task.user.view.search.inherit.hr.timesheet - report.project.task.user - - - - - - - - - - - - - - - - report.project.task.user.view.tree.inherit.hr.timesheet report.project.task.user - + diff --git a/addons/hr_timesheet/static/src/views/timesheet_graph/timesheet_graph_model.js b/addons/hr_timesheet/static/src/views/timesheet_graph/timesheet_graph_model.js index b65559a80d6..040238f5cd2 100644 --- a/addons/hr_timesheet/static/src/views/timesheet_graph/timesheet_graph_model.js +++ b/addons/hr_timesheet/static/src/views/timesheet_graph/timesheet_graph_model.js @@ -4,7 +4,7 @@ import { GraphModel } from "@web/views/graph/graph_model"; const FIELDS = [ 'unit_amount', 'effective_hours', 'planned_hours', 'remaining_hours', 'total_hours_spent', 'subtask_effective_hours', - 'overtime', 'number_hours', 'difference', 'hours_effective', 'hours_planned', 'timesheet_unit_amount' + 'overtime', 'number_hours', 'difference', 'timesheet_unit_amount' ]; export class hrTimesheetGraphModel extends GraphModel { diff --git a/addons/hr_timesheet/tests/test_timesheet.py b/addons/hr_timesheet/tests/test_timesheet.py index d11ce577b92..0f2c3c5e500 100644 --- a/addons/hr_timesheet/tests/test_timesheet.py +++ b/addons/hr_timesheet/tests/test_timesheet.py @@ -546,6 +546,6 @@ class TestTimesheet(TestCommonTimesheet): ('hr_timesheet.project_invoice_form', '//field[@name="allocated_hours"]', [None, 'Allocated Days']), ('hr_timesheet.view_task_form2_inherited', '//field[@name="unit_amount"]', ['Hours Spent', 'Days Spent']), ('hr_timesheet.view_task_tree2_inherited', '//field[@name="planned_hours"]', [None, 'Initially Planned Days']), - ('hr_timesheet.view_task_project_user_graph_inherited', '//field[@name="hours_planned"]', [None, 'Planned Days']), + ('hr_timesheet.view_task_project_user_graph_inherited', '//field[@name="planned_hours"]', [None, 'Initially Planned Days']), ('hr_timesheet.timesheets_analysis_report_pivot_employee', '//field[@name="unit_amount"]', [None, 'Days Spent']), ]) diff --git a/addons/hr_timesheet/views/project_sharing_views.xml b/addons/hr_timesheet/views/project_sharing_views.xml index 86692dd03b3..08751c0ad61 100644 --- a/addons/hr_timesheet/views/project_sharing_views.xml +++ b/addons/hr_timesheet/views/project_sharing_views.xml @@ -140,34 +140,4 @@ - - project.task.tree.inherited - project.task - - - - - - - - - - - - - - - - - project.sharing.project.task.view.search.inherit.timesheet - project.task - - - - - - - - - diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml index b9290592ea7..643ee53acb8 100644 --- a/addons/hr_timesheet/views/project_views.xml +++ b/addons/hr_timesheet/views/project_views.xml @@ -113,9 +113,9 @@ - - - + + + @@ -260,7 +260,7 @@ - + @@ -268,18 +268,17 @@ project.task.tree.inherited project.task - - + - + - + - + @@ -346,27 +345,13 @@ project.task.view.search.inherit.sale.timesheet.enterprise project.task - + + 10 - - - - - - - - - - - - - project.task.view.search.inherit.hr.timesheet - project.task - - - - - + + + + @@ -376,12 +361,8 @@ project.project - - - - - + diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index 64031268abb..d8b04fdd265 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -24,7 +24,6 @@ 'security/ir.model.access.csv', 'security/ir.model.access.xml', 'data/digest_data.xml', - 'report/project_report_views.xml', 'report/project_task_burndown_chart_report_views.xml', 'views/analytic_views.xml', 'views/digest_views.xml', @@ -43,6 +42,7 @@ 'views/project_portal_templates.xml', 'views/project_task_templates.xml', 'views/project_sharing_templates.xml', + 'report/project_report_views.xml', 'data/ir_cron_data.xml', 'data/mail_message_subtype_data.xml', 'data/mail_template_data.xml', diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 4f2e218026d..7327bf3345f 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -47,6 +47,12 @@ PROJECT_TASK_READABLE_FIELDS = { 'allow_milestones', 'milestone_id', 'has_late_and_unreached_milestone', + 'company_id', + 'date_last_stage_update', + 'date_assign', + 'dependent_ids', + 'is_blocked', + 'message_is_follower', } PROJECT_TASK_WRITABLE_FIELDS = { @@ -272,6 +278,12 @@ class Project(models.Model): # Since project stages are order by sequence first, this should fetch the one with the lowest sequence number. return self.env['project.project.stage'].search([], limit=1) + @api.model + def _search_is_favorite(self, operator, value): + if operator not in ['=', '!='] or not isinstance(value, bool): + raise NotImplementedError(_('Operation not supported')) + return [('favorite_user_ids', 'in' if (operator == '=') == value else 'not in', self.env.uid)] + def _compute_is_favorite(self): for project in self: project.is_favorite = self.env.user in project.favorite_user_ids @@ -323,8 +335,8 @@ class Project(models.Model): 'res.users', 'project_favorite_user_rel', 'project_id', 'user_id', default=_get_default_favorite_user_ids, string='Members') - is_favorite = fields.Boolean(compute='_compute_is_favorite', inverse='_inverse_is_favorite', compute_sudo=True, - string='Show Project on Dashboard') + is_favorite = fields.Boolean(compute='_compute_is_favorite', inverse='_inverse_is_favorite', search='_search_is_favorite', + compute_sudo=True, string='Show Project on Dashboard') label_tasks = fields.Char(string='Use Tasks as', default='Tasks', translate=True, help="Name used to refer to the tasks of your project e.g. tasks, tickets, sprints, etc...") tasks = fields.One2many('project.task', 'project_id', string="Task Activities") @@ -507,7 +519,7 @@ class Project(models.Model): read_group = self.env['project.milestone']._read_group([ ('project_id', 'in', self.filtered('allow_milestones').ids), ('is_reached', '=', False), - ('deadline', '<', today)], ['project_id'], ['project_id']) + ('deadline', '<=', today)], ['project_id'], ['project_id']) mapped_count = {group['project_id'][0]: group['project_id_count'] for group in read_group} for project in self: project.is_milestone_exceeded = bool(mapped_count.get(project.id, 0)) @@ -525,7 +537,7 @@ class Project(models.Model): LEFT JOIN project_milestone M ON P.id = M.project_id WHERE M.is_reached IS false AND P.allow_milestones IS true - AND M.deadline < CAST(now() AS date) + AND M.deadline <= CAST(now() AS date) """ if (operator == '=' and value is True) or (operator == '!=' and value is False): operator_new = 'inselect' @@ -764,6 +776,10 @@ class Project(models.Model): def action_project_sharing(self): self.ensure_one() action = self.env['ir.actions.act_window']._for_xml_id('project.project_sharing_project_task_action') + action['context'] = { + 'default_project_id': self.id, + 'delete': False, + } action['display_name'] = self.name return action @@ -2180,7 +2196,7 @@ class Task(models.Model): late_milestones = self.env['project.milestone'].sudo()._search([ # sudo is needed for the portal user in Project Sharing. ('id', 'in', self.milestone_id.ids), ('is_reached', '=', False), - ('deadline', '<', fields.Date.today()), + ('deadline', '<=', fields.Date.today()), ]) for task in self: task.has_late_and_unreached_milestone = task.allow_milestones and task.milestone_id.id in late_milestones diff --git a/addons/project/report/project_report.py b/addons/project/report/project_report.py index 2cdeea7236f..59779da6e28 100644 --- a/addons/project/report/project_report.py +++ b/addons/project/report/project_report.py @@ -34,7 +34,7 @@ class ReportProjectTaskUser(models.Model): ('0', 'Low'), ('1', 'High') ], readonly=True, string="Priority") - state = fields.Selection([ + kanban_state = fields.Selection([ ('normal', 'In Progress'), ('blocked', 'Blocked'), ('done', 'Ready for Next Stage') @@ -49,15 +49,18 @@ class ReportProjectTaskUser(models.Model): column1='project_task_id', column2='project_tags_id', string='Tags', readonly=True) parent_id = fields.Many2one('project.task', string='Parent Task', readonly=True) - ancestor_id = fields.Many2one('project.task', string="Ancestor Task", readonly=True) # We are explicitly not using a related field in order to prevent the recomputing caused by the depends as the model is a report. rating_last_text = fields.Selection(RATING_TEXT, string="Rating Last Text", compute="_compute_rating_last_text", search="_search_rating_last_text") personal_stage_type_ids = fields.Many2many('project.task.type', relation='project_task_user_rel', column1='task_id', column2='stage_id', string="Personal Stage", readonly=True) milestone_id = fields.Many2one('project.milestone', readonly=True) - milestone_reached = fields.Boolean('Is Milestone Reached', readonly=True) - milestone_deadline = fields.Date('Milestone Deadline', readonly=True) + message_is_follower = fields.Boolean(related='task_id.message_is_follower') + is_blocked = fields.Boolean('Is Blocked', readonly=True) + dependent_ids = fields.Many2many('project.task', relation='task_dependencies_rel', column1='depends_on_id', + column2='task_id', string='Block', readonly=True, + domain="[('allow_task_dependencies', '=', True), ('id', '!=', id)]") + description = fields.Text(readonly=True) def _compute_rating_last_text(self): for task_analysis in self: @@ -72,31 +75,33 @@ class ReportProjectTaskUser(models.Model): t.id as id, t.id as task_id, t.active, - t.create_date as create_date, - t.date_assign as date_assign, - t.date_end as date_end, - t.date_last_stage_update as date_last_stage_update, - t.date_deadline as date_deadline, + t.create_date, + t.date_assign, + t.date_end, + t.date_last_stage_update, + t.date_deadline, t.project_id, t.priority, t.name as name, t.company_id, t.partner_id, - t.parent_id as parent_id, - t.ancestor_id as ancestor_id, - t.stage_id as stage_id, - t.is_closed as is_closed, - t.kanban_state as state, + t.parent_id, + t.stage_id, + t.is_closed, + t.kanban_state, t.milestone_id, - pm.is_reached as milestone_reached, - pm.deadline as milestone_deadline, + CASE WHEN pm.id IS NOT NULL THEN true ELSE false END as has_late_and_unreached_milestone, + t.is_blocked, + t.description, NULLIF(t.rating_last_value, 0) as rating_last_value, AVG(rt.rating) as rating_avg, - t.working_days_close as working_days_close, - t.working_days_open as working_days_open, - t.working_hours_open as working_hours_open, - t.working_hours_close as working_hours_close, - (extract('epoch' from (t.date_deadline-(now() at time zone 'UTC'))))/(3600*24) as delay_endings_days + t.working_days_close, + t.working_days_open, + t.working_hours_open, + t.working_hours_close, + (extract('epoch' from (t.date_deadline-(now() at time zone 'UTC'))))/(3600*24) as delay_endings_days, + CASE WHEN t.project_id IS NOT NULL OR t.parent_id IS NOT NULL THEN false ELSE true END as is_private, + COUNT(td.task_id) as dependent_ids_count """ def _group_by(self): @@ -109,7 +114,6 @@ class ReportProjectTaskUser(models.Model): t.date_last_stage_update, t.date_deadline, t.project_id, - t.ancestor_id, t.priority, t.name, t.company_id, @@ -124,18 +128,21 @@ class ReportProjectTaskUser(models.Model): t.working_hours_open, t.working_hours_close, t.milestone_id, - pm.is_reached, - pm.deadline + pm.id, + td.depends_on_id """ def _from(self): return f""" project_task t LEFT JOIN rating_rating rt ON rt.res_id = t.id - AND rt.res_model = 'project.task' - AND rt.consumed = True - AND rt.rating >= {RATING_LIMIT_MIN} + AND rt.res_model = 'project.task' + AND rt.consumed = True + AND rt.rating >= {RATING_LIMIT_MIN} LEFT JOIN project_milestone pm ON pm.id = t.milestone_id + AND pm.is_reached = False + AND pm.deadline <= CAST(now() AS DATE) + LEFT JOIN task_dependencies_rel td ON td.depends_on_id = t.id """ def _where(self): diff --git a/addons/project/report/project_report_views.xml b/addons/project/report/project_report_views.xml index c1a80482f36..730b0f9217b 100644 --- a/addons/project/report/project_report_views.xml +++ b/addons/project/report/project_report_views.xml @@ -41,66 +41,15 @@ report.project.task.user.search report.project.task.user + + primary - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Tasks Analysis + + project_id + @@ -119,4 +68,9 @@ + diff --git a/addons/project/views/project_sharing_views.xml b/addons/project/views/project_sharing_views.xml index 695919629ba..3a50cdf5b54 100644 --- a/addons/project/views/project_sharing_views.xml +++ b/addons/project/views/project_sharing_views.xml @@ -113,28 +113,17 @@ project.sharing.project.task.tree project.task + + primary 999 - - - - - - - - - - - - - - - - - - - + + 0 + 0 + + + @@ -247,42 +236,11 @@ project.task.search.form project.task + + primary 999 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -292,10 +250,6 @@ kanban,tree,form [('display_project_id', '=', active_id)] - { - 'default_project_id': active_id, - 'delete': 0, - }

No tasks found. Let's create one! diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 8719f36f11e..1f46cc8bcbb 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -11,52 +11,96 @@ - + + project.task.search.form project.task - + - - - + + - - - + - - - - + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + project.task.search.form.project.base + project.task + + primary + + + + + + + + + + + + + + + + + + + project.task.search.form.project.base + project.task + + primary + + + + + + + + + + + + project.task.search.form + project.task + + primary + + + + + + @@ -64,41 +108,7 @@ domain="[('my_activity_date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/> - - - - - - - - - - - - - - - - - project.task.search.form.extended - project.task - - primary - - - - - - - - - - - - - - - + @@ -644,27 +654,17 @@ - - + - - - - - - - - - - - + + + @@ -689,20 +690,16 @@ project.project - + - + - - + - - - - - + + @@ -1197,8 +1194,8 @@ @@ -1239,7 +1236,7 @@ + context="{'default_project_id' : project_id, 'search_view_ref' : 'project.view_task_search_form', 'search_default_display_project_id': project_id}"> @@ -1329,7 +1326,7 @@ - + @@ -1493,13 +1490,13 @@ - - project.task.tree + + + project.task.view.tree.main.base project.task - - - + + @@ -1507,35 +1504,62 @@ - - - + + + - - - - - - - - + + + + + + + + + + + + + + project.task.view.tree.base + project.task + + primary + + + 1 + project_task_list + stage_id + + + + - - - - - + + + + + project.task.tree + project.task + + primary + 2 + + + + + project.task.calendar project.task @@ -1662,6 +1686,18 @@ project.task primary + + + + + + + + + open.view.all.tasks.list.view + project.task + + primary personal_stage_type_ids @@ -1691,8 +1727,9 @@ My Tasks project.task kanban,tree,form,calendar,pivot,graph,activity - {'search_default_my_tasks': 1, 'search_default_open_tasks': 1, 'all_task': 0, 'default_user_ids': [(4, uid)]} - + {'search_default_my_tasks': 1, 'all_task': 0, 'default_user_ids': [(4, uid)]} + + [('user_ids', 'in', uid)]

No tasks found. Let's create one! @@ -1738,8 +1775,8 @@ All Tasks project.task tree,kanban,form,calendar,pivot,graph,activity - {'search_default_open_tasks': 1, 'default_user_ids': [(4, uid)]} - + {'default_user_ids': [(4, uid)]} +

No tasks found. Let's create one! @@ -1755,7 +1792,7 @@ tree - + @@ -1789,7 +1826,7 @@ Tasks project.task kanban,tree,form - + @@ -1798,7 +1835,7 @@ tree,form,calendar,graph,kanban [('is_closed', '=', False), ('date_deadline','<',time.strftime('%Y-%m-%d')), ('display_project_id', '!=', False)] - + @@ -1906,12 +1943,6 @@ - - - - - - report.project.task.user.search.inherited - report.project.task.user - - - - - - - - - - - - diff --git a/addons/sale_project/views/project_sharing_views.xml b/addons/sale_project/views/project_sharing_views.xml index 4c3e2344e0b..59fe6e587fe 100644 --- a/addons/sale_project/views/project_sharing_views.xml +++ b/addons/sale_project/views/project_sharing_views.xml @@ -25,18 +25,4 @@ - - project.task.search.inherit - project.task - - - - - - - - - - - diff --git a/addons/sale_project/views/project_task_views.xml b/addons/sale_project/views/project_task_views.xml index 8fe09e94d00..59c6e6e243a 100644 --- a/addons/sale_project/views/project_task_views.xml +++ b/addons/sale_project/views/project_task_views.xml @@ -1,6 +1,22 @@ + + {'sale_show_partner_name': True} + + + + {'sale_show_partner_name': True} + + + + {'sale_show_partner_name': True} + + + + {'sale_show_partner_name': True} + + project.project.select.inherit.project project.project @@ -86,7 +102,7 @@ project.task.form.inherit.sale.project project.task - + @@ -97,14 +113,11 @@ project.task.search.inherit project.task - + - - - - - - + + + diff --git a/addons/sale_project/views/project_views.xml b/addons/sale_project/views/project_views.xml index 8f1f4839f53..c2d263f76e0 100644 --- a/addons/sale_project/views/project_views.xml +++ b/addons/sale_project/views/project_views.xml @@ -1,15 +1,15 @@ - {'default_allow_billable': True} + {'default_allow_billable': True, 'sale_show_partner_name': True} - {'default_allow_billable': True} + {'default_allow_billable': True, 'sale_show_partner_name': True} - {'default_allow_billable': True} + {'default_allow_billable': True, 'sale_show_partner_name': True} - {'default_allow_billable': True, 'search_default_groupby_stage': 1} + {'default_allow_billable': True, 'sale_show_partner_name': True, 'search_default_groupby_stage': 1} diff --git a/addons/sale_timesheet/report/__init__.py b/addons/sale_timesheet/report/__init__.py index 50dc678f4bc..6c55a4362c2 100644 --- a/addons/sale_timesheet/report/__init__.py +++ b/addons/sale_timesheet/report/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import project_report from . import timesheets_analysis_report diff --git a/addons/sale_timesheet/report/project_report.py b/addons/sale_timesheet/report/project_report.py new file mode 100644 index 00000000000..d3d530291ed --- /dev/null +++ b/addons/sale_timesheet/report/project_report.py @@ -0,0 +1,24 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details + +from odoo import fields, models + + +class ReportProjectTaskUser(models.Model): + _inherit = 'report.project.task.user' + + remaining_hours_so = fields.Float('Remaining Hours on SO', readonly=True) + + def _select(self): + return super()._select() + """, + sol.remaining_hours as remaining_hours_so + """ + + def _group_by(self): + return super()._group_by() + """, + sol.remaining_hours + """ + + def _from(self): + return super()._from() + """ + LEFT JOIN sale_order_line sol ON t.id = sol.task_id + """ diff --git a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js index 07265bb6cb4..9c38693a955 100644 --- a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js +++ b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js @@ -144,10 +144,6 @@ tour.register('sale_timesheet_tour', { }, { trigger: 'div[name="partner_id"] ul > li:first-child > a:contains(Freeman)', content: 'Select the customer in the autocomplete dropdown', -}, { - trigger: 'a.nav-link[name="billing_employee_rate"]', - extra_trigger: 'div.o_notebook_headers', - content: 'Click on Invoicing tab to configure the invoicing of this project.', }, { trigger: 'div[name="sale_line_id"] input', content: 'Select a Sales Order Item as Default Sales Order Item for each task in this project.', @@ -155,6 +151,10 @@ tour.register('sale_timesheet_tour', { }, { trigger: '[name="sale_line_id"] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))', content: 'Select the Sales Order Item in the autocomplete dropdown.', +}, { + trigger: 'a.nav-link[name="billing_employee_rate"]', + extra_trigger: 'div.o_notebook_headers', + content: 'Click on Invoicing tab to configure the invoicing of this project.', }, { trigger: 'div[name="sale_line_employee_ids"] td.o_field_x2many_list_row_add > a[role="button"]', content: 'Click on Add a line on the mapping list view.', @@ -195,9 +195,9 @@ tour.register('sale_timesheet_tour', { trigger: 'div[name="partner_id"] ul > li:first-child > a:contains(Freeman)', content: 'Select the customer in the autocomplete dropdown', }, { - trigger: 'a.nav-link[name="billing_employee_rate"]', + trigger: 'a.nav-link[name="settings"]', extra_trigger: 'div.o_notebook_headers', - content: 'Click on Invoicing tab to configure the invoicing of this project.', + content: 'Click on Settings tab to configure this project.', }, { trigger: 'div[name="sale_line_id"] input', content: 'Select the first sale order of the list', diff --git a/addons/sale_timesheet/views/project_sharing_views.xml b/addons/sale_timesheet/views/project_sharing_views.xml index eb7b87a5697..cd1f8c86744 100644 --- a/addons/sale_timesheet/views/project_sharing_views.xml +++ b/addons/sale_timesheet/views/project_sharing_views.xml @@ -39,15 +39,4 @@ - - project.task.tree.inherit.sale.timesheet - project.task - - - - - - - - diff --git a/addons/sale_timesheet/views/project_task_views.xml b/addons/sale_timesheet/views/project_task_views.xml index a519b1058be..ef7e6262a71 100644 --- a/addons/sale_timesheet/views/project_task_views.xml +++ b/addons/sale_timesheet/views/project_task_views.xml @@ -6,18 +6,12 @@ project.project + + + + - - - - - - - - - - @@ -190,4 +184,15 @@ + + + project.task.view.search.inherit + project.task + + + + ['|', ('overtime', '>', 0), ('remaining_hours_so', '<', 0)] + + + diff --git a/addons/spreadsheet_dashboard_hr_timesheet/data/files/tasks_dashboard.json b/addons/spreadsheet_dashboard_hr_timesheet/data/files/tasks_dashboard.json index 5ddee3c493b..bf083b4e821 100644 --- a/addons/spreadsheet_dashboard_hr_timesheet/data/files/tasks_dashboard.json +++ b/addons/spreadsheet_dashboard_hr_timesheet/data/files/tasks_dashboard.json @@ -50,12 +50,12 @@ "cells": { "A7": { "style": 1, - "content": "[Tasks by Stage](odoo://view/{\"viewType\":\"graph\",\"action\":{\"domain\":[],\"context\":{\"group_by\":[\"stage_id\",\"state\"],\"graph_measure\":\"__count\",\"graph_mode\":\"bar\",\"graph_groupbys\":[\"stage_id\",\"state\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", + "content": "[Tasks by Stage](odoo://view/{\"viewType\":\"graph\",\"action\":{\"domain\":[],\"context\":{\"group_by\":[\"stage_id\",\"kanban_state\"],\"graph_measure\":\"__count\",\"graph_mode\":\"bar\",\"graph_groupbys\":[\"stage_id\",\"kanban_state\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", "border": 1 }, "A19": { "style": 1, - "content": "[Top Assignees](odoo://view/{\"viewType\":\"pivot\",\"action\":{\"domain\":[[\"user_ids\",\"!=\",false]],\"context\":{\"group_by\":[],\"pivot_measures\":[\"hours_effective\",\"nbr\"],\"pivot_column_groupby\":[],\"pivot_row_groupby\":[\"user_ids\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", + "content": "[Top Assignees](odoo://view/{\"viewType\":\"pivot\",\"action\":{\"domain\":[[\"user_ids\",\"!=\",false]],\"context\":{\"group_by\":[],\"pivot_measures\":[\"effective_hours\",\"nbr\"],\"pivot_column_groupby\":[],\"pivot_row_groupby\":[\"user_ids\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", "border": 1 }, "A20": { @@ -105,7 +105,7 @@ }, "A32": { "style": 1, - "content": "[Top Tags](odoo://view/{\"viewType\":\"pivot\",\"action\":{\"domain\":[[\"tag_ids\",\"!=\",false]],\"context\":{\"group_by\":[\"tag_ids\"],\"pivot_measures\":[\"hours_effective\",\"nbr\"],\"pivot_column_groupby\":[],\"pivot_row_groupby\":[\"tag_ids\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", + "content": "[Top Tags](odoo://view/{\"viewType\":\"pivot\",\"action\":{\"domain\":[[\"tag_ids\",\"!=\",false]],\"context\":{\"group_by\":[\"tag_ids\"],\"pivot_measures\":[\"effective_hours\",\"nbr\"],\"pivot_column_groupby\":[],\"pivot_row_groupby\":[\"tag_ids\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", "border": 1 }, "A33": { @@ -169,47 +169,47 @@ "B21": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",1)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",1)" }, "B22": { "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",2)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",2)" }, "B23": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",3)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",3)" }, "B24": { "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",4)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",4)" }, "B25": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",5)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",5)" }, "B26": { "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",6)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",6)" }, "B27": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",7)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",7)" }, "B28": { "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",8)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",8)" }, "B29": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",9)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",9)" }, "B30": { "format": 1, - "content": "=ODOO.PIVOT(1,\"hours_effective\",\"#user_ids\",10)" + "content": "=ODOO.PIVOT(1,\"effective_hours\",\"#user_ids\",10)" }, "B32": { "style": 5, @@ -223,47 +223,47 @@ "B34": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",1)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",1)" }, "B35": { "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",2)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",2)" }, "B36": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",3)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",3)" }, "B37": { "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",4)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",4)" }, "B38": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",5)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",5)" }, "B39": { "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",6)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",6)" }, "B40": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",7)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",7)" }, "B41": { "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",8)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",8)" }, "B42": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",9)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",9)" }, "B43": { "format": 1, - "content": "=ODOO.PIVOT(3,\"hours_effective\",\"#tag_ids\",10)" + "content": "=ODOO.PIVOT(3,\"effective_hours\",\"#tag_ids\",10)" }, "C7": { "style": 5, @@ -369,12 +369,12 @@ }, "E7": { "style": 1, - "content": "[Tasks by State](odoo://view/{\"viewType\":\"graph\",\"action\":{\"domain\":[],\"context\":{\"group_by\":[\"state\"],\"graph_measure\":\"__count\",\"graph_mode\":\"pie\",\"graph_groupbys\":[\"state\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", + "content": "[Tasks by State](odoo://view/{\"viewType\":\"graph\",\"action\":{\"domain\":[],\"context\":{\"group_by\":[\"kanban_state\"],\"graph_measure\":\"__count\",\"graph_mode\":\"pie\",\"graph_groupbys\":[\"kanban_state\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", "border": 1 }, "E19": { "style": 1, - "content": "[Top Projects](odoo://view/{\"viewType\":\"pivot\",\"action\":{\"domain\":[[\"project_id\",\"!=\",false]],\"context\":{\"group_by\":[\"project_id\"],\"pivot_measures\":[\"hours_effective\",\"nbr\"],\"pivot_column_groupby\":[],\"pivot_row_groupby\":[\"project_id\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", + "content": "[Top Projects](odoo://view/{\"viewType\":\"pivot\",\"action\":{\"domain\":[[\"project_id\",\"!=\",false]],\"context\":{\"group_by\":[\"project_id\"],\"pivot_measures\":[\"effective_hours\",\"nbr\"],\"pivot_column_groupby\":[],\"pivot_row_groupby\":[\"project_id\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", "border": 1 }, "E20": { @@ -424,7 +424,7 @@ }, "E32": { "style": 1, - "content": "[Top Customers](odoo://view/{\"viewType\":\"pivot\",\"action\":{\"domain\":[[\"partner_id\",\"!=\",false]],\"context\":{\"group_by\":[\"partner_id\"],\"pivot_measures\":[\"hours_effective\",\"nbr\"],\"pivot_column_groupby\":[],\"pivot_row_groupby\":[\"partner_id\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", + "content": "[Top Customers](odoo://view/{\"viewType\":\"pivot\",\"action\":{\"domain\":[[\"partner_id\",\"!=\",false]],\"context\":{\"group_by\":[\"partner_id\"],\"pivot_measures\":[\"effective_hours\",\"nbr\"],\"pivot_column_groupby\":[],\"pivot_row_groupby\":[\"partner_id\"]},\"modelName\":\"report.project.task.user\",\"views\":[[false,\"graph\"],[false,\"pivot\"],[false,\"list\"],[false,\"search\"]]},\"threshold\":0,\"name\":\"Tasks Analysis\"})", "border": 1 }, "E33": { @@ -488,47 +488,47 @@ "F21": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",1)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",1)" }, "F22": { "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",2)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",2)" }, "F23": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",3)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",3)" }, "F24": { "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",4)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",4)" }, "F25": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",5)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",5)" }, "F26": { "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",6)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",6)" }, "F27": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",7)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",7)" }, "F28": { "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",8)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",8)" }, "F29": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",9)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",9)" }, "F30": { "format": 1, - "content": "=ODOO.PIVOT(2,\"hours_effective\",\"#project_id\",10)" + "content": "=ODOO.PIVOT(2,\"effective_hours\",\"#project_id\",10)" }, "F32": { "style": 5, @@ -542,47 +542,47 @@ "F34": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",1)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",1)" }, "F35": { "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",2)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",2)" }, "F36": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",3)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",3)" }, "F37": { "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",4)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",4)" }, "F38": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",5)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",5)" }, "F39": { "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",6)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",6)" }, "F40": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",7)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",7)" }, "F41": { "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",8)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",8)" }, "F42": { "style": 7, "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",9)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",9)" }, "F43": { "format": 1, - "content": "=ODOO.PIVOT(4,\"hours_effective\",\"#partner_id\",10)" + "content": "=ODOO.PIVOT(4,\"effective_hours\",\"#partner_id\",10)" }, "G7": { "style": 5, @@ -732,7 +732,7 @@ "metaData": { "groupBy": [ "stage_id", - "state" + "kanban_state" ], "measure": "__count", "order": null, @@ -748,7 +748,7 @@ "domain": [], "groupBy": [ "stage_id", - "state" + "kanban_state" ], "orderBy": [] }, @@ -771,7 +771,7 @@ "legendPosition": "right", "metaData": { "groupBy": [ - "state" + "kanban_state" ], "measure": "__count", "order": null, @@ -786,7 +786,7 @@ }, "domain": [], "groupBy": [ - "state" + "kanban_state" ], "orderBy": [] }, @@ -920,7 +920,7 @@ "content": "=ODOO.PIVOT(5,\"nbr\")" }, "B3": { - "content": "=ODOO.PIVOT(5,\"hours_effective\")" + "content": "=ODOO.PIVOT(5,\"effective_hours\")" }, "B4": { "content": "=ODOO.PIVOT(5,\"working_days_open\")" @@ -936,7 +936,7 @@ "content": "=ODOO.PIVOT(6,\"nbr\")" }, "C3": { - "content": "=ODOO.PIVOT(6,\"hours_effective\")" + "content": "=ODOO.PIVOT(6,\"effective_hours\")" }, "C4": { "content": "=ODOO.PIVOT(6,\"working_days_open\")" @@ -1078,7 +1078,7 @@ "id": "1", "measures": [ { - "field": "hours_effective" + "field": "effective_hours" }, { "field": "nbr" @@ -1115,7 +1115,7 @@ "id": "2", "measures": [ { - "field": "hours_effective" + "field": "effective_hours" }, { "field": "nbr" @@ -1152,7 +1152,7 @@ "id": "3", "measures": [ { - "field": "hours_effective" + "field": "effective_hours" }, { "field": "nbr" @@ -1189,7 +1189,7 @@ "id": "4", "measures": [ { - "field": "hours_effective" + "field": "effective_hours" }, { "field": "nbr" @@ -1223,7 +1223,7 @@ "field": "nbr" }, { - "field": "hours_effective" + "field": "effective_hours" }, { "field": "working_days_open" @@ -1258,7 +1258,7 @@ "field": "nbr" }, { - "field": "hours_effective" + "field": "effective_hours" }, { "field": "working_days_open"