217 lines
10 KiB
Python
217 lines
10 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.exceptions import ValidationError
|
||
|
|
||
|
|
||
|
class ProductTemplate(models.Model):
|
||
|
_inherit = 'product.template'
|
||
|
|
||
|
@api.model
|
||
|
def _selection_service_policy(self):
|
||
|
service_policies = [
|
||
|
# (service_policy, string)
|
||
|
('ordered_prepaid', _('Prepaid/Fixed Price')),
|
||
|
('delivered_manual', _('Based on Delivered Quantity (Manual)')),
|
||
|
]
|
||
|
if self.user_has_groups('project.group_project_milestone'):
|
||
|
service_policies.insert(1, ('delivered_milestones', _('Based on Milestones')))
|
||
|
return service_policies
|
||
|
|
||
|
service_tracking = fields.Selection(
|
||
|
selection=[
|
||
|
('no', 'Nothing'),
|
||
|
('task_global_project', 'Task'),
|
||
|
('task_in_project', 'Project & Task'),
|
||
|
('project_only', 'Project'),
|
||
|
],
|
||
|
string="Create on Order", default="no",
|
||
|
help="On Sales order confirmation, this product can generate a project and/or task. \
|
||
|
From those, you can track the service you are selling.\n \
|
||
|
'In sale order\'s project': Will use the sale order\'s configured project if defined or fallback to \
|
||
|
creating a new project based on the selected template.")
|
||
|
project_id = fields.Many2one(
|
||
|
'project.project', 'Project', company_dependent=True,
|
||
|
)
|
||
|
project_template_id = fields.Many2one(
|
||
|
'project.project', 'Project Template', company_dependent=True, copy=True,
|
||
|
)
|
||
|
service_policy = fields.Selection('_selection_service_policy', string="Service Invoicing Policy", compute='_compute_service_policy', inverse='_inverse_service_policy')
|
||
|
service_type = fields.Selection(selection_add=[
|
||
|
('milestones', 'Project Milestones'),
|
||
|
])
|
||
|
|
||
|
@api.depends('invoice_policy', 'service_type', 'type')
|
||
|
def _compute_service_policy(self):
|
||
|
for product in self:
|
||
|
product.service_policy = self._get_general_to_service(product.invoice_policy, product.service_type)
|
||
|
if not product.service_policy and product.type == 'service':
|
||
|
product.service_policy = 'ordered_prepaid'
|
||
|
|
||
|
@api.depends('service_tracking', 'service_policy', 'type', 'sale_ok')
|
||
|
def _compute_product_tooltip(self):
|
||
|
super()._compute_product_tooltip()
|
||
|
for record in self.filtered(lambda record: record.type == 'service' and record.sale_ok):
|
||
|
if record.service_policy == 'ordered_prepaid':
|
||
|
if record.service_tracking == 'no':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice ordered quantities as soon as this service is sold."
|
||
|
)
|
||
|
elif record.service_tracking == 'task_global_project':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice ordered quantities as soon as this service is sold. "
|
||
|
"Create a task in an existing project to track the time spent."
|
||
|
)
|
||
|
elif record.service_tracking == 'project_only':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice ordered quantities as soon as this service is sold. "
|
||
|
"Create a project for the order with a task for each sales order line "
|
||
|
"to track the time spent."
|
||
|
)
|
||
|
elif record.service_tracking == 'task_in_project':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice ordered quantities as soon as this service is sold. "
|
||
|
"Create an empty project for the order to track the time spent."
|
||
|
)
|
||
|
elif record.service_policy == 'delivered_milestones':
|
||
|
if record.service_tracking == 'no':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice your milestones when they are reached."
|
||
|
)
|
||
|
elif record.service_tracking == 'task_global_project':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice your milestones when they are reached. "
|
||
|
"Create a task in an existing project to track the time spent."
|
||
|
)
|
||
|
elif record.service_tracking == 'project_only':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice your milestones when they are reached. "
|
||
|
"Create a project for the order with a task for each sales order line "
|
||
|
"to track the time spent."
|
||
|
)
|
||
|
elif record.service_tracking == 'task_in_project':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice your milestones when they are reached. "
|
||
|
"Create an empty project for the order to track the time spent."
|
||
|
)
|
||
|
elif record.service_policy == 'delivered_manual':
|
||
|
if record.service_tracking == 'no':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice this service when it is delivered (set the quantity by hand on your sales order lines). "
|
||
|
)
|
||
|
elif record.service_tracking == 'task_global_project':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice this service when it is delivered (set the quantity by hand on your sales order lines). "
|
||
|
"Create a task in an existing project to track the time spent."
|
||
|
)
|
||
|
elif record.service_tracking == 'project_only':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice this service when it is delivered (set the quantity by hand on your sales order lines). "
|
||
|
"Create a project for the order with a task for each sales order line "
|
||
|
"to track the time spent."
|
||
|
)
|
||
|
elif record.service_tracking == 'task_in_project':
|
||
|
record.product_tooltip = _(
|
||
|
"Invoice this service when it is delivered (set the quantity by hand on your sales order lines). "
|
||
|
"Create an empty project for the order to track the time spent."
|
||
|
)
|
||
|
|
||
|
def _get_service_to_general_map(self):
|
||
|
return {
|
||
|
# service_policy: (invoice_policy, service_type)
|
||
|
'ordered_prepaid': ('order', 'manual'),
|
||
|
'delivered_milestones': ('delivery', 'milestones'),
|
||
|
'delivered_manual': ('delivery', 'manual'),
|
||
|
}
|
||
|
|
||
|
def _get_general_to_service_map(self):
|
||
|
return {v: k for k, v in self._get_service_to_general_map().items()}
|
||
|
|
||
|
def _get_service_to_general(self, service_policy):
|
||
|
return self._get_service_to_general_map().get(service_policy, (False, False))
|
||
|
|
||
|
def _get_general_to_service(self, invoice_policy, service_type):
|
||
|
general_to_service = self._get_general_to_service_map()
|
||
|
return general_to_service.get((invoice_policy, service_type), False)
|
||
|
|
||
|
@api.onchange('service_policy')
|
||
|
def _inverse_service_policy(self):
|
||
|
for product in self:
|
||
|
if product.service_policy:
|
||
|
product.invoice_policy, product.service_type = self._get_service_to_general(product.service_policy)
|
||
|
|
||
|
@api.constrains('project_id', 'project_template_id')
|
||
|
def _check_project_and_template(self):
|
||
|
""" NOTE 'service_tracking' should be in decorator parameters but since ORM check constraints twice (one after setting
|
||
|
stored fields, one after setting non stored field), the error is raised when company-dependent fields are not set.
|
||
|
So, this constraints does cover all cases and inconsistent can still be recorded until the ORM change its behavior.
|
||
|
"""
|
||
|
for product in self:
|
||
|
if product.service_tracking == 'no' and (product.project_id or product.project_template_id):
|
||
|
raise ValidationError(_('The product %s should not have a project nor a project template since it will not generate project.', product.name))
|
||
|
elif product.service_tracking == 'task_global_project' and product.project_template_id:
|
||
|
raise ValidationError(_('The product %s should not have a project template since it will generate a task in a global project.', product.name))
|
||
|
elif product.service_tracking in ['task_in_project', 'project_only'] and product.project_id:
|
||
|
raise ValidationError(_('The product %s should not have a global project since it will generate a project.', product.name))
|
||
|
|
||
|
@api.onchange('service_tracking')
|
||
|
def _onchange_service_tracking(self):
|
||
|
if self.service_tracking == 'no':
|
||
|
self.project_id = False
|
||
|
self.project_template_id = False
|
||
|
elif self.service_tracking == 'task_global_project':
|
||
|
self.project_template_id = False
|
||
|
elif self.service_tracking in ['task_in_project', 'project_only']:
|
||
|
self.project_id = False
|
||
|
|
||
|
@api.onchange('type')
|
||
|
def _onchange_type(self):
|
||
|
res = super(ProductTemplate, self)._onchange_type()
|
||
|
if self.type != 'service':
|
||
|
self.service_tracking = 'no'
|
||
|
return res
|
||
|
|
||
|
def write(self, vals):
|
||
|
if 'type' in vals and vals['type'] != 'service':
|
||
|
vals.update({
|
||
|
'service_tracking': 'no',
|
||
|
'project_id': False
|
||
|
})
|
||
|
return super(ProductTemplate, self).write(vals)
|
||
|
|
||
|
|
||
|
class ProductProduct(models.Model):
|
||
|
_inherit = 'product.product'
|
||
|
|
||
|
@api.onchange('service_tracking')
|
||
|
def _onchange_service_tracking(self):
|
||
|
if self.service_tracking == 'no':
|
||
|
self.project_id = False
|
||
|
self.project_template_id = False
|
||
|
elif self.service_tracking == 'task_global_project':
|
||
|
self.project_template_id = False
|
||
|
elif self.service_tracking in ['task_in_project', 'project_only']:
|
||
|
self.project_id = False
|
||
|
|
||
|
def _inverse_service_policy(self):
|
||
|
for product in self:
|
||
|
if product.service_policy:
|
||
|
|
||
|
product.invoice_policy, product.service_type = self.product_tmpl_id._get_service_to_general(product.service_policy)
|
||
|
|
||
|
@api.onchange('type')
|
||
|
def _onchange_type(self):
|
||
|
res = super(ProductProduct, self)._onchange_type()
|
||
|
if self.type != 'service':
|
||
|
self.service_tracking = 'no'
|
||
|
return res
|
||
|
|
||
|
def write(self, vals):
|
||
|
if 'type' in vals and vals['type'] != 'service':
|
||
|
vals.update({
|
||
|
'service_tracking': 'no',
|
||
|
'project_id': False
|
||
|
})
|
||
|
return super(ProductProduct, self).write(vals)
|