forked from Mapan/odoo17e
183 lines
8.3 KiB
Python
183 lines
8.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.osv import expression
|
|
from odoo.tools import SQL
|
|
|
|
|
|
class Project(models.Model):
|
|
_inherit = "project.project"
|
|
|
|
allow_material = fields.Boolean("Products on Tasks", compute="_compute_allow_material", store=True, readonly=False)
|
|
allow_quotations = fields.Boolean(
|
|
"Extra Quotations", compute="_compute_allow_quotations", store=True, readonly=False)
|
|
allow_billable = fields.Boolean(
|
|
compute='_compute_allow_billable', store=True, readonly=False)
|
|
sale_line_id = fields.Many2one(
|
|
compute="_compute_sale_line_id", store=True, readonly=False)
|
|
|
|
_sql_constraints = [
|
|
('material_imply_billable', "CHECK((allow_material = 't' AND allow_billable = 't') OR (allow_material = 'f'))", 'The material can be allowed only when the task can be billed.'),
|
|
('fsm_imply_task_rate', "CHECK((is_fsm = 't' AND sale_line_id IS NULL) OR (is_fsm = 'f'))", 'An FSM project must be billed at task rate or employee rate.'),
|
|
('timesheet_product_required_if_billable_and_time', """
|
|
CHECK(
|
|
(allow_billable = 't' AND allow_timesheets = 't' AND is_fsm = 't' AND timesheet_product_id IS NOT NULL)
|
|
OR (allow_billable IS NOT TRUE)
|
|
OR (allow_timesheets IS NOT TRUE)
|
|
OR (is_fsm IS NOT TRUE)
|
|
OR (allow_billable IS NULL)
|
|
OR (allow_timesheets IS NULL)
|
|
OR (is_fsm IS NULL)
|
|
)""", 'The timesheet product is required when the fsm project can be billed and timesheets are allowed.'),
|
|
]
|
|
|
|
def _get_hide_partner(self):
|
|
return super()._get_hide_partner() and not self.is_fsm
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
defaults = super().default_get(fields_list)
|
|
if 'allow_quotations' in fields_list and 'allow_quotations' not in defaults and defaults.get('is_fsm'):
|
|
defaults['allow_quotations'] = self.env.user.has_group('industry_fsm.group_fsm_quotation_from_task')
|
|
return defaults
|
|
|
|
@api.depends('is_fsm')
|
|
def _compute_allow_quotations(self):
|
|
if not self.env.user.has_group('industry_fsm.group_fsm_quotation_from_task'):
|
|
self.allow_quotations = False
|
|
else:
|
|
for project in self:
|
|
project.allow_quotations = project.is_fsm
|
|
|
|
@api.depends('is_fsm', 'allow_material')
|
|
def _compute_allow_billable(self):
|
|
for project in self:
|
|
project.allow_billable = project.allow_billable or project.is_fsm or project.allow_material
|
|
|
|
@api.depends('allow_billable', 'is_fsm')
|
|
def _compute_allow_material(self):
|
|
for project in self:
|
|
project.allow_material = project.allow_billable and project.is_fsm
|
|
|
|
def flush_model(self, fnames=None):
|
|
if fnames is not None:
|
|
# force 'allow_billable' and 'allow_material' to be flushed
|
|
# altogether in order to satisfy the SQL constraint above
|
|
fnames = set(fnames)
|
|
if 'allow_billable' in fnames or 'allow_material' in fnames:
|
|
fnames.add('allow_billable')
|
|
fnames.add('allow_material')
|
|
return super().flush_model(fnames)
|
|
|
|
def flush_recordset(self, fnames=None):
|
|
if fnames is not None:
|
|
# force 'allow_billable' and 'allow_material' to be flushed
|
|
# altogether in order to satisfy the SQL constraint above
|
|
fnames = set(fnames)
|
|
if 'allow_billable' in fnames or 'allow_material' in fnames:
|
|
fnames.add('allow_billable')
|
|
fnames.add('allow_material')
|
|
return super().flush_recordset(fnames)
|
|
|
|
@api.depends('sale_line_id', 'sale_line_employee_ids', 'allow_billable', 'is_fsm')
|
|
def _compute_pricing_type(self):
|
|
fsm_projects = self.filtered(lambda project: project.allow_billable and project.is_fsm)
|
|
for fsm_project in fsm_projects:
|
|
if fsm_project.sale_line_employee_ids:
|
|
fsm_project.update({'pricing_type': 'employee_rate'})
|
|
else:
|
|
fsm_project.update({'pricing_type': 'task_rate'})
|
|
super(Project, self - fsm_projects)._compute_pricing_type()
|
|
|
|
def _search_pricing_type(self, operator, value):
|
|
domain = super()._search_pricing_type(operator, value)
|
|
if value == 'fixed_rate':
|
|
fsm_domain = [('is_fsm', operator, False)]
|
|
if operator == '=':
|
|
domain = expression.AND([fsm_domain, domain])
|
|
else:
|
|
domain = expression.OR([fsm_domain, domain])
|
|
elif value in ['task_rate', 'employee_rate']:
|
|
fsm_domain = [
|
|
('is_fsm', '=', True),
|
|
('allow_billable', '=', True),
|
|
('sale_line_employee_ids', '!=' if value == 'employee_rate' else '=', False),
|
|
]
|
|
if operator == '=':
|
|
domain = expression.OR([fsm_domain, domain])
|
|
else:
|
|
fsm_domain = expression.normalize_domain(fsm_domain)
|
|
fsm_domain.insert(0, expression.NOT_OPERATOR)
|
|
domain = expression.AND([expression.distribute_not(fsm_domain), domain])
|
|
return domain
|
|
|
|
@api.depends('is_fsm')
|
|
def _compute_sale_line_id(self):
|
|
# We cannot have a SOL in the fsm project
|
|
fsm_projects = self.filtered('is_fsm')
|
|
fsm_projects.update({'sale_line_id': False})
|
|
super(Project, self - fsm_projects)._compute_sale_line_id()
|
|
|
|
@api.depends('sale_line_employee_ids.sale_line_id', 'sale_line_id')
|
|
def _compute_partner_id(self):
|
|
basic_projects = self.filtered(lambda project: not project.is_fsm)
|
|
super(Project, basic_projects)._compute_partner_id()
|
|
|
|
@api.depends('is_fsm')
|
|
def _compute_display_sales_stat_buttons(self):
|
|
fsm_projects = self.filtered('is_fsm')
|
|
fsm_projects.display_sales_stat_buttons = False
|
|
super(Project, self - fsm_projects)._compute_display_sales_stat_buttons()
|
|
|
|
def _get_profitability_sale_order_items_domain(self, domain=None):
|
|
quotation_projects = self.filtered('allow_quotations')
|
|
if quotation_projects:
|
|
include_additional_sale_orders = [
|
|
('order_id', 'in', quotation_projects._get_additional_quotations([
|
|
('state', '=', 'sale'),
|
|
]).ids)
|
|
]
|
|
domain = include_additional_sale_orders \
|
|
if domain is None \
|
|
else expression.OR([domain, include_additional_sale_orders])
|
|
return super()._get_profitability_sale_order_items_domain(domain)
|
|
|
|
def _get_additional_quotations_query(self, domain=None):
|
|
if domain is None:
|
|
domain = []
|
|
SaleOrder = self.env['sale.order']
|
|
query = SaleOrder._where_calc(expression.AND([domain, [('task_id', '!=', False)]]))
|
|
SaleOrder._apply_ir_rules(query, 'read')
|
|
task_alias = query.make_alias(SaleOrder._table, 'task_id')
|
|
query.add_join("JOIN", task_alias, 'project_task', SQL(
|
|
"%s = %s AND %s IN %s",
|
|
SQL.identifier(SaleOrder._table, 'task_id'),
|
|
SQL.identifier(task_alias, 'id'),
|
|
SQL.identifier(task_alias, 'project_id'),
|
|
tuple(self.ids),
|
|
))
|
|
return query
|
|
|
|
def _get_additional_quotations(self, domain=None):
|
|
return self.env['sale.order'].browse(self._get_additional_quotations_query(domain))
|
|
|
|
def _get_sale_order_items_query(self, domain_per_model=None):
|
|
basic_project_domain = [('is_fsm', '=', False)] # when the project is a fsm one, no SOL is linked to that project.
|
|
employee_mapping_domain = [('project_id.is_fsm', '=', False)]
|
|
if domain_per_model is None:
|
|
domain_per_model = {
|
|
'project.project': basic_project_domain,
|
|
'project.sale.line.employee.map': employee_mapping_domain,
|
|
}
|
|
else:
|
|
domain_per_model['project.project'] = expression.AND([
|
|
domain_per_model.get('project.project', []),
|
|
basic_project_domain,
|
|
])
|
|
domain_per_model['project.sale.line.employee.map'] = expression.AND([
|
|
domain_per_model.get('project.sale.line.employee.map', []),
|
|
employee_mapping_domain,
|
|
])
|
|
return super()._get_sale_order_items_query(domain_per_model)
|