diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py index 18f510038f5..3c040734b14 100644 --- a/addons/hr_timesheet/models/project.py +++ b/addons/hr_timesheet/models/project.py @@ -27,6 +27,7 @@ class Project(models.Model): compute='_compute_total_timesheet_time', help="Total number of time (in the proper UoM) recorded in the project, rounded to the unit.") encode_uom_in_days = fields.Boolean(compute='_compute_encode_uom_in_days') + is_internal_project = fields.Boolean(compute='_compute_is_internal_project', search='_search_is_internal_project') def _compute_encode_uom_in_days(self): self.encode_uom_in_days = self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day') @@ -36,6 +37,29 @@ class Project(models.Model): without_account = self.filtered(lambda t: not t.analytic_account_id and t._origin) without_account.update({'allow_timesheets': False}) + @api.depends('company_id') + def _compute_is_internal_project(self): + for project in self: + project.is_internal_project = bool(project.company_id.internal_project_id) + + @api.model + def _search_is_internal_project(self, operator, value): + if not isinstance(value, bool): + raise ValueError('Invalid value: %s' % (value)) + if operator not in ['=', '!=']: + raise ValueError('Invalid operator: %s' % (operator)) + + query = """ + SELECT C.internal_project_id + FROM res_company C + WHERE C.internal_project_id IS NOT NULL + """ + if (operator == '=' and value is True) or (operator == '!=' and value is False): + operator_new = 'inselect' + else: + operator_new = 'not inselect' + return [('id', operator_new, (query, ()))] + @api.constrains('allow_timesheets', 'analytic_account_id') def _check_allow_timesheet(self): for project in self: diff --git a/addons/hr_timesheet/models/res_company.py b/addons/hr_timesheet/models/res_company.py index 3d09c0d1459..888c8ae7225 100644 --- a/addons/hr_timesheet/models/res_company.py +++ b/addons/hr_timesheet/models/res_company.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError class ResCompany(models.Model): @@ -36,6 +37,14 @@ class ResCompany(models.Model): default=_default_timesheet_encode_uom_id, domain=lambda self: [('category_id', '=', self.env.ref('uom.uom_categ_wtime').id)], help="""This will set the unit of measure used to encode timesheet. This will simply provide tools and widgets to help the encoding. All reporting will still be expressed in hours (default value).""") + internal_project_id = fields.Many2one( + 'project.project', string="Internal Project", + help="Default project value for timesheet generated from time off type.") + + @api.constrains('internal_project_id') + def _check_internal_project_id_company(self): + if self.filtered(lambda company: company.internal_project_id and company.internal_project_id.sudo().company_id != company): + raise ValidationError(_('The Internal Project of a company should be in that company.')) @api.model_create_multi def create(self, values): @@ -62,6 +71,9 @@ class ResCompany(models.Model): }) for name in [_('Training'), _('Meeting')]] }] project_ids = self.env['project.project'].create(results) + projects_by_company = {project.company_id.id: project for project in project_ids} + for company in self: + company.internal_project_id = projects_by_company.get(company.id, False) return project_ids def _is_timesheet_hour_uom(self): diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml index 4318fce9238..ab241157ecd 100644 --- a/addons/hr_timesheet/views/project_views.xml +++ b/addons/hr_timesheet/views/project_views.xml @@ -322,5 +322,9 @@ + + [('is_internal_project', '=', False)] + + diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 24ab24461da..4aa12abb8f4 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -692,7 +692,7 @@ class Task(models.Model): kanban_state = fields.Selection([ ('normal', 'In Progress'), ('done', 'Ready'), - ('blocked', 'Blocked')], string='Kanban State', + ('blocked', 'Blocked')], string='Status', copy=False, default='normal', required=True) kanban_state_label = fields.Char(compute='_compute_kanban_state_label', string='Kanban State Label', tracking=True) create_date = fields.Datetime("Created On", readonly=True, index=True) diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 9171bc348e8..f412bc341b9 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -439,7 +439,7 @@ - + diff --git a/addons/project_timesheet_holidays/__init__.py b/addons/project_timesheet_holidays/__init__.py index 388eef6f439..5d919761b32 100644 --- a/addons/project_timesheet_holidays/__init__.py +++ b/addons/project_timesheet_holidays/__init__.py @@ -15,6 +15,6 @@ def post_init(cr, registry): for hr_leave_type in env['hr.leave.type'].search([('timesheet_generate', '=', True), ('timesheet_project_id', '=', False)]): company = hr_leave_type.company_id or env.company hr_leave_type.write({ - 'timesheet_project_id': company.leave_timesheet_project_id.id, + 'timesheet_project_id': company.internal_project_id.id, 'timesheet_task_id': company.leave_timesheet_task_id.id, }) diff --git a/addons/project_timesheet_holidays/models/hr_holidays.py b/addons/project_timesheet_holidays/models/hr_holidays.py index 634736b2ba6..a1f8878a3ad 100644 --- a/addons/project_timesheet_holidays/models/hr_holidays.py +++ b/addons/project_timesheet_holidays/models/hr_holidays.py @@ -10,7 +10,7 @@ class HolidaysType(models.Model): def _default_project_id(self): company = self.company_id if self.company_id else self.env.company - return company.leave_timesheet_project_id.id + return company.internal_project_id.id def _default_task_id(self): company = self.company_id if self.company_id else self.env.company diff --git a/addons/project_timesheet_holidays/models/res_company.py b/addons/project_timesheet_holidays/models/res_company.py index 292df9dae60..b9287f38490 100644 --- a/addons/project_timesheet_holidays/models/res_company.py +++ b/addons/project_timesheet_holidays/models/res_company.py @@ -1,50 +1,44 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models, _ -from odoo.exceptions import ValidationError +from odoo import fields, models, _ class Company(models.Model): _inherit = 'res.company' - leave_timesheet_project_id = fields.Many2one( - 'project.project', string="Internal Project", - help="Default project value for timesheet generated from time off type.") leave_timesheet_task_id = fields.Many2one( 'project.task', string="Time Off Task", - domain="[('project_id', '=', leave_timesheet_project_id)]") - - @api.constrains('leave_timesheet_project_id') - def _check_leave_timesheet_project_id_company(self): - for company in self: - if company.leave_timesheet_project_id: - if company.leave_timesheet_project_id.sudo().company_id != company: - raise ValidationError(_('The Internal Project of a company should be in that company.')) + domain="[('project_id', '=', internal_project_id)]") def init(self): type_ids = [(4, self.env.ref('hr_timesheet.internal_project_default_stage').id)] - for company in self.search([('leave_timesheet_project_id', '=', False)]): + companies = self.search(['|', ('internal_project_id', '=', False), ('leave_timesheet_task_id', '=', False)]) + internal_projects_by_company_dict = None + Project = self.env['project.project'] + for company in companies: company = company.with_company(company) - project = company.env['project.project'].search([ - ('name', '=', _('Internal')), - ('allow_timesheets', '=', True), - ('company_id', '=', company.id), - ], limit=1) - if not project: - project = company.env['project.project'].create({ - 'name': _('Internal'), - 'allow_timesheets': True, - 'company_id': company.id, - 'type_ids': type_ids, - }) - company.write({ - 'leave_timesheet_project_id': project.id, - }) + if not company.internal_project_id: + if not internal_projects_by_company_dict: + internal_projects_by_company_read = Project.search_read([ + ('name', '=', _('Internal')), + ('allow_timesheets', '=', True), + ('company_id', 'in', companies.ids), + ], ['company_id', 'id']) + internal_projects_by_company_dict = {res['company_id'][0]: res['id'] for res in internal_projects_by_company_read} + project_id = internal_projects_by_company_dict.get(company.id, False) + if not project_id: + project_id = Project.create({ + 'name': _('Internal'), + 'allow_timesheets': True, + 'company_id': company.id, + 'type_ids': type_ids, + }).id + company.write({'internal_project_id': project_id}) if not company.leave_timesheet_task_id: task = company.env['project.task'].create({ 'name': _('Time Off'), - 'project_id': company.leave_timesheet_project_id.id, + 'project_id': company.internal_project_id.id, 'active': True, 'company_id': company.id, }) @@ -57,17 +51,14 @@ class Company(models.Model): for project in projects: company = project.company_id company = company.with_company(company) - if not company.leave_timesheet_project_id: - company.write({ - 'leave_timesheet_project_id': project.id, - }) if not company.leave_timesheet_task_id: task = company.env['project.task'].sudo().create({ 'name': _('Time Off'), - 'project_id': company.leave_timesheet_project_id.id, + 'project_id': company.internal_project_id.id, 'active': True, 'company_id': company.id, }) company.write({ 'leave_timesheet_task_id': task.id, }) + return projects diff --git a/addons/project_timesheet_holidays/models/res_config_settings.py b/addons/project_timesheet_holidays/models/res_config_settings.py index c4b864ab26d..a2ff473b59e 100644 --- a/addons/project_timesheet_holidays/models/res_config_settings.py +++ b/addons/project_timesheet_holidays/models/res_config_settings.py @@ -7,19 +7,19 @@ from odoo import api, fields, models class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' - leave_timesheet_project_id = fields.Many2one( - related='company_id.leave_timesheet_project_id', required=True, string="Internal Project", + internal_project_id = fields.Many2one( + related='company_id.internal_project_id', required=True, string="Internal Project", domain="[('company_id', '=', company_id)]", readonly=False) leave_timesheet_task_id = fields.Many2one( related='company_id.leave_timesheet_task_id', string="Time Off Task", readonly=False, - domain="[('company_id', '=', company_id), ('project_id', '=?', leave_timesheet_project_id)]") + domain="[('company_id', '=', company_id), ('project_id', '=?', internal_project_id)]") - @api.onchange('leave_timesheet_project_id') + @api.onchange('internal_project_id') def _onchange_timesheet_project_id(self): - if self.leave_timesheet_project_id != self.leave_timesheet_task_id.project_id: + if self.internal_project_id != self.leave_timesheet_task_id.project_id: self.leave_timesheet_task_id = False @api.onchange('leave_timesheet_task_id') def _onchange_timesheet_task_id(self): if self.leave_timesheet_task_id: - self.leave_timesheet_project_id = self.leave_timesheet_task_id.project_id + self.internal_project_id = self.leave_timesheet_task_id.project_id diff --git a/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py b/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py index e999b84e8ae..60a66f857c0 100644 --- a/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py +++ b/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py @@ -20,7 +20,7 @@ class TestTimesheetHolidaysCreate(common.TransactionCase): }) company = self.env.company - self.assertEqual(status.timesheet_project_id, company.leave_timesheet_project_id, 'The default project linked to the status should be the same as the company') + self.assertEqual(status.timesheet_project_id, company.internal_project_id, 'The default project linked to the status should be the same as the company') self.assertEqual(status.timesheet_task_id, company.leave_timesheet_task_id, 'The default task linked to the status should be the same as the company') def test_company_create(self): @@ -33,7 +33,7 @@ class TestTimesheetHolidaysCreate(common.TransactionCase): Company = Company.with_user(user) Company = Company.with_company(main_company) company = Company.create({'name': "Wall Company"}) - self.assertEqual(company.leave_timesheet_project_id.sudo().company_id, company, "It should have created a project for the company") + self.assertEqual(company.internal_project_id.sudo().company_id, company, "It should have created a project for the company") class TestTimesheetHolidays(TestCommonTimesheet): @@ -48,7 +48,7 @@ class TestTimesheetHolidays(TestCommonTimesheet): self.leave_end_datetime = self.leave_start_datetime + relativedelta(days=3) # all company have those internal project/task (created by default) - self.internal_project = self.env.company.leave_timesheet_project_id + self.internal_project = self.env.company.internal_project_id self.internal_task_leaves = self.env.company.leave_timesheet_task_id self.hr_leave_type_with_ts = self.env['hr.leave.type'].create({ diff --git a/addons/project_timesheet_holidays/views/res_config_settings_views.xml b/addons/project_timesheet_holidays/views/res_config_settings_views.xml index 1b9aa047e34..5f99c09e562 100644 --- a/addons/project_timesheet_holidays/views/res_config_settings_views.xml +++ b/addons/project_timesheet_holidays/views/res_config_settings_views.xml @@ -10,12 +10,12 @@
-