diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml index 7ca73edf687..3114143c394 100644 --- a/addons/hr_timesheet/views/project_views.xml +++ b/addons/hr_timesheet/views/project_views.xml @@ -59,9 +59,19 @@ 24 - - - + + + + + + + + + Log time on tasks + + + + diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 3b78c60fb55..bf8be85842c 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -1060,8 +1060,6 @@ class Task(models.Model): @api.model def _default_personal_stage_type_id(self): - if self._context.get('default_project_id'): - return False return self.env['project.task.type'].search([('user_id', '=', self.env.user.id)], limit=1).id @api.model diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index b8f9c159fa9..b841b708983 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -323,14 +323,25 @@ - - - + + project.task.type.tree.inherited + project.task.type + primary + + + + + + + + + + project.task.type.kanban project.task.type @@ -364,7 +375,7 @@ Task Stages project.task.type tree,kanban,form - + [('user_id', '=', False)] @@ -380,7 +391,7 @@ project.task.type tree,kanban,form [('project_ids','=', project_id)] - + Create a new stage in the task pipeline @@ -515,8 +526,8 @@ - - + + @@ -756,13 +767,16 @@ project.project - - - - + + + + + + + + - + @ @@ -778,9 +792,9 @@ primary - + @@ -943,6 +957,18 @@ + + project.kanban.inherit.config.project + project.project + primary + + + + + + + + project.project.calendar project.project @@ -1014,7 +1040,7 @@ tree,kanban,form + (0, 0, {'view_mode': 'kanban', 'view_id': ref('view_project_config_kanban')})]"/> {} @@ -1055,7 +1081,7 @@ kanban - + @@ -1314,6 +1340,7 @@ + diff --git a/addons/project_sms/views/project_task_type_views.xml b/addons/project_sms/views/project_task_type_views.xml index ae039c31b52..5b44e499a28 100644 --- a/addons/project_sms/views/project_task_type_views.xml +++ b/addons/project_sms/views/project_task_type_views.xml @@ -15,7 +15,7 @@ project.task.type.view.tree.inherit.project.sms project.task.type - + diff --git a/addons/sale_timesheet/models/project.py b/addons/sale_timesheet/models/project.py index 1f2de9379ff..d96081ef78d 100644 --- a/addons/sale_timesheet/models/project.py +++ b/addons/sale_timesheet/models/project.py @@ -56,6 +56,14 @@ class Project(models.Model): compute='_compute_partner_id', store=True, readonly=False) allocated_hours = fields.Float(compute='_compute_allocated_hours', store=True, readonly=False, copy=False) + @api.model + def _get_view(self, view_id=None, view_type='form', **options): + arch, view = super()._get_view(view_id, view_type, **options) + if view_type == 'form' and self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day'): + for node in arch.xpath("//field[@name='display_cost'][not(@string)]"): + node.set('string', 'Daily Cost') + return arch, view + @api.depends('sale_line_id', 'sale_line_employee_ids', 'allow_billable') def _compute_pricing_type(self): billable_projects = self.filtered('allow_billable') diff --git a/addons/sale_timesheet/models/project_sale_line_employee_map.py b/addons/sale_timesheet/models/project_sale_line_employee_map.py index 94703b9e3ae..f2fa4b8fafe 100644 --- a/addons/sale_timesheet/models/project_sale_line_employee_map.py +++ b/addons/sale_timesheet/models/project_sale_line_employee_map.py @@ -25,6 +25,7 @@ class ProjectProductEmployeeMap(models.Model): currency_id = fields.Many2one('res.currency', string="Currency", compute='_compute_currency_id', store=True, readonly=False) cost = fields.Monetary(currency_field='cost_currency_id', compute='_compute_cost', store=True, readonly=False, help="This cost overrides the employee's default timesheet cost in employee's HR Settings") + display_cost = fields.Monetary(currency_field='cost_currency_id', compute="_compute_display_cost", inverse="_inverse_display_cost", string="Hourly Cost") cost_currency_id = fields.Many2one('res.currency', string="Cost Currency", related='employee_id.currency_id', readonly=True) is_cost_changed = fields.Boolean('Is Cost Manually Changed', compute='_compute_is_cost_changed', store=True) @@ -61,6 +62,45 @@ class ProjectProductEmployeeMap(models.Model): if not map_entry.is_cost_changed: map_entry.cost = map_entry.employee_id.timesheet_cost or 0.0 + def _get_working_hours_per_calendar(self, is_uom_day=False): + resource_calendar_per_hours = {} + + if not is_uom_day: + return resource_calendar_per_hours + + read_group_data = self.env['resource.calendar']._read_group( + [('id', 'in', self.employee_id.resource_calendar_id.ids)], + ['ids:array_agg(id)', 'hours_per_day'], + ['hours_per_day'], + ) + for res in read_group_data: + for calendar_id in res.get('ids', []): + resource_calendar_per_hours[calendar_id] = res.get('hours_per_day') + + return resource_calendar_per_hours + + @api.depends_context('company') + @api.depends('cost', 'employee_id.resource_calendar_id') + def _compute_display_cost(self): + is_uom_day = self.env.ref('uom.product_uom_day') == self.env.company.timesheet_encode_uom_id + resource_calendar_per_hours = self._get_working_hours_per_calendar(is_uom_day) + + for map_line in self: + if is_uom_day: + map_line.display_cost = map_line.cost * resource_calendar_per_hours.get(map_line.employee_id.resource_calendar_id.id, 1) + else: + map_line.display_cost = map_line.cost + + def _inverse_display_cost(self): + is_uom_day = self.env.ref('uom.product_uom_day') == self.env.company.timesheet_encode_uom_id + resource_calendar_per_hours = self._get_working_hours_per_calendar(is_uom_day) + + for map_line in self: + if is_uom_day: + map_line.cost = map_line.display_cost / resource_calendar_per_hours.get(map_line.employee_id.resource_calendar_id.id, 1) + else: + map_line.cost = map_line.display_cost + @api.depends('cost') def _compute_is_cost_changed(self): for map_entry in self: diff --git a/addons/sale_timesheet/views/project_task_views.xml b/addons/sale_timesheet/views/project_task_views.xml index c3ed86d0e7b..7382fc9b283 100644 --- a/addons/sale_timesheet/views/project_task_views.xml +++ b/addons/sale_timesheet/views/project_task_views.xml @@ -42,7 +42,7 @@ - + @@ -58,10 +58,20 @@ project.project - - - - + + + + + + + + + + Invoice your time and material to customers + + + +
@@ -380,7 +391,7 @@ project.task.type tree,kanban,form [('project_ids','=', project_id)] - + Create a new stage in the task pipeline @@ -515,8 +526,8 @@ - - + + @@ -756,13 +767,16 @@ project.project - - - - + + + + + + + + - + @ @@ -778,9 +792,9 @@ primary - + @@ -943,6 +957,18 @@ + + project.kanban.inherit.config.project + project.project + primary + + + + + + + + project.project.calendar project.project @@ -1014,7 +1040,7 @@ tree,kanban,form + (0, 0, {'view_mode': 'kanban', 'view_id': ref('view_project_config_kanban')})]"/> {} @@ -1055,7 +1081,7 @@ kanban - + @@ -1314,6 +1340,7 @@ + diff --git a/addons/project_sms/views/project_task_type_views.xml b/addons/project_sms/views/project_task_type_views.xml index ae039c31b52..5b44e499a28 100644 --- a/addons/project_sms/views/project_task_type_views.xml +++ b/addons/project_sms/views/project_task_type_views.xml @@ -15,7 +15,7 @@ project.task.type.view.tree.inherit.project.sms project.task.type - + diff --git a/addons/sale_timesheet/models/project.py b/addons/sale_timesheet/models/project.py index 1f2de9379ff..d96081ef78d 100644 --- a/addons/sale_timesheet/models/project.py +++ b/addons/sale_timesheet/models/project.py @@ -56,6 +56,14 @@ class Project(models.Model): compute='_compute_partner_id', store=True, readonly=False) allocated_hours = fields.Float(compute='_compute_allocated_hours', store=True, readonly=False, copy=False) + @api.model + def _get_view(self, view_id=None, view_type='form', **options): + arch, view = super()._get_view(view_id, view_type, **options) + if view_type == 'form' and self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day'): + for node in arch.xpath("//field[@name='display_cost'][not(@string)]"): + node.set('string', 'Daily Cost') + return arch, view + @api.depends('sale_line_id', 'sale_line_employee_ids', 'allow_billable') def _compute_pricing_type(self): billable_projects = self.filtered('allow_billable') diff --git a/addons/sale_timesheet/models/project_sale_line_employee_map.py b/addons/sale_timesheet/models/project_sale_line_employee_map.py index 94703b9e3ae..f2fa4b8fafe 100644 --- a/addons/sale_timesheet/models/project_sale_line_employee_map.py +++ b/addons/sale_timesheet/models/project_sale_line_employee_map.py @@ -25,6 +25,7 @@ class ProjectProductEmployeeMap(models.Model): currency_id = fields.Many2one('res.currency', string="Currency", compute='_compute_currency_id', store=True, readonly=False) cost = fields.Monetary(currency_field='cost_currency_id', compute='_compute_cost', store=True, readonly=False, help="This cost overrides the employee's default timesheet cost in employee's HR Settings") + display_cost = fields.Monetary(currency_field='cost_currency_id', compute="_compute_display_cost", inverse="_inverse_display_cost", string="Hourly Cost") cost_currency_id = fields.Many2one('res.currency', string="Cost Currency", related='employee_id.currency_id', readonly=True) is_cost_changed = fields.Boolean('Is Cost Manually Changed', compute='_compute_is_cost_changed', store=True) @@ -61,6 +62,45 @@ class ProjectProductEmployeeMap(models.Model): if not map_entry.is_cost_changed: map_entry.cost = map_entry.employee_id.timesheet_cost or 0.0 + def _get_working_hours_per_calendar(self, is_uom_day=False): + resource_calendar_per_hours = {} + + if not is_uom_day: + return resource_calendar_per_hours + + read_group_data = self.env['resource.calendar']._read_group( + [('id', 'in', self.employee_id.resource_calendar_id.ids)], + ['ids:array_agg(id)', 'hours_per_day'], + ['hours_per_day'], + ) + for res in read_group_data: + for calendar_id in res.get('ids', []): + resource_calendar_per_hours[calendar_id] = res.get('hours_per_day') + + return resource_calendar_per_hours + + @api.depends_context('company') + @api.depends('cost', 'employee_id.resource_calendar_id') + def _compute_display_cost(self): + is_uom_day = self.env.ref('uom.product_uom_day') == self.env.company.timesheet_encode_uom_id + resource_calendar_per_hours = self._get_working_hours_per_calendar(is_uom_day) + + for map_line in self: + if is_uom_day: + map_line.display_cost = map_line.cost * resource_calendar_per_hours.get(map_line.employee_id.resource_calendar_id.id, 1) + else: + map_line.display_cost = map_line.cost + + def _inverse_display_cost(self): + is_uom_day = self.env.ref('uom.product_uom_day') == self.env.company.timesheet_encode_uom_id + resource_calendar_per_hours = self._get_working_hours_per_calendar(is_uom_day) + + for map_line in self: + if is_uom_day: + map_line.cost = map_line.display_cost / resource_calendar_per_hours.get(map_line.employee_id.resource_calendar_id.id, 1) + else: + map_line.cost = map_line.display_cost + @api.depends('cost') def _compute_is_cost_changed(self): for map_entry in self: diff --git a/addons/sale_timesheet/views/project_task_views.xml b/addons/sale_timesheet/views/project_task_views.xml index c3ed86d0e7b..7382fc9b283 100644 --- a/addons/sale_timesheet/views/project_task_views.xml +++ b/addons/sale_timesheet/views/project_task_views.xml @@ -42,7 +42,7 @@ - + @@ -58,10 +58,20 @@ project.project - - - - + + + + + + + + + + Invoice your time and material to customers + + + +
Create a new stage in the task pipeline @@ -515,8 +526,8 @@