From b061644e0c4519f4a45351fa9650a0637e7e18e4 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Fri, 24 May 2019 13:01:18 +0000 Subject: [PATCH 1/6] [IMP] sale: set company required The sales order is a important model. Since it generates invoices and other business documents, the company_id field should be required as the company must be propagate to generated documents. Moreover, a sales order without a company raises a error, and we need a company to compute the multi currency rate. This commit fixes that bug, by setting the company_id field required on sales order and sale order line. Note: we only simplify the ir.rule of sale.order, and not the one of sale.order.line. Indeed, when creating an SOL, the creation is done in 2 times by the ORM: first insert, then UPDATE with computed stored fields. As company_id of SOL is a stored related, the check access rules raises during the inconsistent state (between INSERT and UPDATE, company_id is False). Maybe this will be clean after the big refactoring of the ORM. Task-1999686 --- addons/sale/models/sale.py | 2 +- addons/sale/security/sale_security.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 4e197509019..aa770884e07 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -174,7 +174,7 @@ class SaleOrder(models.Model): payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms', oldname='payment_term', help="Payment terms applies to all invoices issued from this order.") fiscal_position_id = fields.Many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position', help=" Fiscal positions are used to adapt taxes and accounts for particular customers or sales orders/invoices. The default value comes from the customer.") - company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company) + company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company) team_id = fields.Many2one('crm.team', 'Sales Team', change_default=True, default=_get_default_team, oldname='section_id') signature = fields.Binary('Signature', help='Signature received through the portal.', copy=False, attachment=True) diff --git a/addons/sale/security/sale_security.xml b/addons/sale/security/sale_security.xml index 129046dac20..44fe53fa1e8 100644 --- a/addons/sale/security/sale_security.xml +++ b/addons/sale/security/sale_security.xml @@ -42,14 +42,14 @@ Sales Order multi-company - ['|',('company_id','=',False),('company_id', 'in', company_ids)] + [('company_id', 'in', company_ids)] Sales Order Line multi-company - ['|',('company_id','=',False),('company_id', 'in', company_ids)] + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] From 938990f6984d7432842bddaac878f731cf2decf9 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Fri, 24 May 2019 13:03:09 +0000 Subject: [PATCH 2/6] [IMP] project: company required on task As task can be billed, it became a important business model. To avoid mistakes in a multi company environment, we need to make the company_id field required. Indeed shared task can be problematic with access rights when a employee will log timesheet from another company in a task that is not in the same company as its project. To populate this field, we recommend to take the company of task's project, or to fallback on the company of the user that created one. Task-1999686 --- addons/project/models/project.py | 4 +--- addons/project/security/project_security.xml | 12 +++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/addons/project/models/project.py b/addons/project/models/project.py index ad874ebfdab..3f64b22eedb 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -519,9 +519,7 @@ class Task(models.Model): string='Customer', default=lambda self: self._get_default_partner()) manager_id = fields.Many2one('res.users', string='Project Manager', related='project_id.user_id', readonly=True, related_sudo=False) - company_id = fields.Many2one('res.company', - string='Company', - default=lambda self: self.env.company) + company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company) color = fields.Integer(string='Color Index') user_email = fields.Char(related='user_id.email', string='User Email', readonly=True, related_sudo=False) attachment_ids = fields.One2many('ir.attachment', compute='_compute_attachment_ids', string="Main Attachments", diff --git a/addons/project/security/project_security.xml b/addons/project/security/project_security.xml index c50386bb6ec..eecc68d1ccb 100644 --- a/addons/project/security/project_security.xml +++ b/addons/project/security/project_security.xml @@ -38,10 +38,7 @@ Project: multi-company - ['|', - ('company_id', '=', False), - ('company_id', 'in', company_ids), - ] + [('company_id', 'in', company_ids)] @@ -67,10 +64,7 @@ Project/Task: multi-company - ['|', - ('company_id', '=', False), - ('company_id', 'in', company_ids), - ] + [('company_id', 'in', company_ids)] @@ -100,7 +94,7 @@ Task Analysis multi-company - ['|',('company_id','=',False),('company_id', 'in', company_ids)] + [('company_id', 'in', company_ids)] From ddb13a520fdc0be60be05c831b61ba5716d3d891 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Fri, 24 May 2019 13:44:00 +0000 Subject: [PATCH 3/6] [FIX] sale_timesheet: propagate company On Sales order confirmation, a project can be create. The project company is not correctly propagated: the created project should be in the same company as the SO. To stay consistent, during the sold quantity conversion into the planned hours of a task, we need to use the SO company instead of the one of current user. Task-1999686 --- addons/sale_timesheet/models/sale_order.py | 9 +++++---- .../sale_timesheet/wizard/project_create_sale_order.py | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/addons/sale_timesheet/models/sale_order.py b/addons/sale_timesheet/models/sale_order.py index dd903ba7330..70c45218e9b 100644 --- a/addons/sale_timesheet/models/sale_order.py +++ b/addons/sale_timesheet/models/sale_order.py @@ -212,7 +212,7 @@ class SaleOrderLine(models.Model): if 'product_uom_qty' in values: for line in self: if line.task_id: - planned_hours = line._convert_qty_company_hours() + planned_hours = line._convert_qty_company_hours(line.task_id.company_id) line.task_id.write({'planned_hours': planned_hours}) return result @@ -220,8 +220,8 @@ class SaleOrderLine(models.Model): # Service : Project and task generation ########################################### - def _convert_qty_company_hours(self): - company_time_uom_id = self.env.company.project_time_mode_id + def _convert_qty_company_hours(self, dest_company): + company_time_uom_id = dest_company.project_time_mode_id if self.product_uom.id != company_time_uom_id.id and self.product_uom.category_id.id == company_time_uom_id.category_id.id: planned_hours = self.product_uom._compute_quantity(self.product_uom_qty, company_time_uom_id) else: @@ -249,6 +249,7 @@ class SaleOrderLine(models.Model): 'sale_line_id': self.id, 'sale_order_id': self.order_id.id, 'active': True, + 'company_id': self.company_id.id, } if self.product_id.project_template_id: values['name'] = "%s - %s" % (values['name'], self.product_id.project_template_id.name) @@ -270,7 +271,7 @@ class SaleOrderLine(models.Model): def _timesheet_create_task_prepare_values(self, project): self.ensure_one() - planned_hours = self._convert_qty_company_hours() + planned_hours = self._convert_qty_company_hours(self.company_id) sale_line_name_parts = self.name.split('\n') title = sale_line_name_parts[0] or self.product_id.name description = '
'.join(sale_line_name_parts[1:]) diff --git a/addons/sale_timesheet/wizard/project_create_sale_order.py b/addons/sale_timesheet/wizard/project_create_sale_order.py index 11c473517b1..364e3056ac3 100644 --- a/addons/sale_timesheet/wizard/project_create_sale_order.py +++ b/addons/sale_timesheet/wizard/project_create_sale_order.py @@ -76,6 +76,7 @@ class ProjectCreateSalesOrder(models.TransientModel): 'partner_id': self.partner_id.id, 'analytic_account_id': self.project_id.analytic_account_id.id, 'client_order_ref': self.project_id.name, + 'company_id': self.project_id.company_id.id, }) sale_order.onchange_partner_id() sale_order.onchange_partner_shipping_id() From 26bc10486eb58ca0adc850c1f0483aff41d689e7 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Fri, 24 May 2019 15:08:42 +0000 Subject: [PATCH 4/6] [FIX] project: progate company in project on AA creation When allowing timesheet on a project, the project generate an analytic account if not given. When creating that account, the company should be the one of the project (origin document) rather than the one of the current user. Task-1999686 --- addons/project/models/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 3f64b22eedb..f9a9f53c406 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -408,7 +408,7 @@ class Project(models.Model): def _create_analytic_account_from_values(self, values): analytic_account = self.env['account.analytic.account'].create({ 'name': values.get('name', _('Unknown Analytic Account')), - 'company_id': values.get('company_id', self.env.user.company_id.id), + 'company_id': values.get('company_id') or self.env.company.id, 'partner_id': values.get('partner_id'), 'active': True, }) From ef1afb7b69610fa3ff783751ee544d7a0d020841 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Mon, 27 May 2019 09:51:06 +0000 Subject: [PATCH 5/6] [FIX] project_timesheet_holidays: multi company fixes In a multi company environment, some adjustment must be done: - the company of an internal project of a company should be in that company - the generate timesheet on holidays validation must be in the company the task (or internal project) The company should be correctly propagate to timesheets. Task-1999686 --- .../project_timesheet_holidays/models/hr_holidays.py | 1 + .../project_timesheet_holidays/models/res_company.py | 10 +++++++++- .../models/res_config_settings.py | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/addons/project_timesheet_holidays/models/hr_holidays.py b/addons/project_timesheet_holidays/models/hr_holidays.py index ada3b56acc1..e78420a0176 100644 --- a/addons/project_timesheet_holidays/models/hr_holidays.py +++ b/addons/project_timesheet_holidays/models/hr_holidays.py @@ -83,6 +83,7 @@ class Holidays(models.Model): 'date': fields.Date.to_string(day_date), 'holiday_id': holiday.id, 'employee_id': holiday.employee_id.id, + 'company_id': holiday_task.company_id.id or holiday_project.company_id.id, }) return super(Holidays, self)._validate_leave_request() diff --git a/addons/project_timesheet_holidays/models/res_company.py b/addons/project_timesheet_holidays/models/res_company.py index 948b4f3bffa..56b59e1b022 100644 --- a/addons/project_timesheet_holidays/models/res_company.py +++ b/addons/project_timesheet_holidays/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 Company(models.Model): @@ -14,6 +15,13 @@ class Company(models.Model): '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.company_id != company: + raise ValidationError(_('The Internal Project of a company should be in that company.')) + def init(self): self.search([('leave_timesheet_project_id', '=', False)])._create_leave_project_task() @@ -40,7 +48,7 @@ class Company(models.Model): 'name': _('Time Off'), 'project_id': company.leave_timesheet_project_id.id, 'active': False, - 'company_id': False, + 'company_id': company.id, }) company.write({ 'leave_timesheet_task_id': task.id, diff --git a/addons/project_timesheet_holidays/models/res_config_settings.py b/addons/project_timesheet_holidays/models/res_config_settings.py index bbc0b0a12ce..2bf86555488 100644 --- a/addons/project_timesheet_holidays/models/res_config_settings.py +++ b/addons/project_timesheet_holidays/models/res_config_settings.py @@ -7,5 +7,5 @@ from odoo import fields, models class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' - leave_timesheet_project_id = fields.Many2one(related='company_id.leave_timesheet_project_id', string="Internal Project", readonly=False) - leave_timesheet_task_id = fields.Many2one(related='company_id.leave_timesheet_task_id', string="Time Off Task", readonly=False) + leave_timesheet_project_id = fields.Many2one(related='company_id.leave_timesheet_project_id', 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", domain="[('company_id', '=', company_id)]", readonly=False) From 2511841da1c42622b2d83f2235417bb83d14a76a Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Mon, 17 Jun 2019 12:13:47 +0000 Subject: [PATCH 6/6] [IMP] project: company field not in debug mode We want to display company_id field on project form view only when user has multi company access group, like every other document in Odoo Task-1999686 --- addons/project/views/project_views.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 035f70f4e5f..cbd14f7caa1 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -151,15 +151,12 @@ + - - - -