# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import threading from odoo import api, fields, models, _ from odoo.exceptions import ValidationError class ProductTemplate(models.Model): _inherit = 'product.template' service_policy = fields.Selection([ ('ordered_timesheet', 'Prepaid'), ('delivered_timesheet', 'Timesheets on tasks'), ('delivered_manual', 'Milestones (manually set quantities on order)') ], string="Service Invoicing Policy", compute='_compute_service_policy', inverse='_inverse_service_policy') service_type = fields.Selection(selection_add=[ ('timesheet', 'Timesheets on project (one fare per SO/Project)'), ], ondelete={'timesheet': 'set default'}) # override domain project_id = fields.Many2one(domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('bill_type', '=', 'customer_task'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]") project_template_id = fields.Many2one(domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('bill_type', '=', 'customer_project'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]") def _default_visible_expense_policy(self): visibility = self.user_has_groups('project.group_project_user') return visibility or super(ProductTemplate, self)._default_visible_expense_policy() def _compute_visible_expense_policy(self): visibility = self.user_has_groups('project.group_project_user') for product_template in self: if not product_template.visible_expense_policy: product_template.visible_expense_policy = visibility return super(ProductTemplate, self)._compute_visible_expense_policy() @api.depends('invoice_policy', 'service_type') def _compute_service_policy(self): for product in self: policy = None if product.invoice_policy == 'delivery': policy = 'delivered_manual' if product.service_type == 'manual' else 'delivered_timesheet' elif product.invoice_policy == 'order' and (product.service_type == 'timesheet' or product.type == 'service'): policy = 'ordered_timesheet' product.service_policy = policy def _inverse_service_policy(self): for product in self: policy = product.service_policy if not policy and not product.invoice_policy =='delivery': product.invoice_policy = 'order' product.service_type = 'manual' elif policy == 'ordered_timesheet': product.invoice_policy = 'order' product.service_type = 'timesheet' else: product.invoice_policy = 'delivery' product.service_type = 'manual' if policy == 'delivered_manual' else 'timesheet' @api.onchange('type') def _onchange_type(self): res = super(ProductTemplate, self)._onchange_type() if self.type == 'service' and not self.invoice_policy: self.invoice_policy = 'order' self.service_type = 'timesheet' elif self.type == 'service' and self.invoice_policy == 'order': self.service_policy = 'ordered_timesheet' elif self.type == 'consu' and not self.invoice_policy and self.service_policy == 'ordered_timesheet': self.invoice_policy = 'order' return res @api.model def _get_onchange_service_policy_updates(self, service_tracking, service_policy, project_id, project_template_id): vals = {} if service_tracking != 'no' and service_policy == 'delivered_timesheet': if project_id and not project_id.allow_timesheets: vals['project_id'] = False elif project_template_id and not project_template_id.allow_timesheets: vals['project_template_id'] = False return vals @api.onchange('service_policy') def _onchange_service_policy(self): vals = self._get_onchange_service_policy_updates(self.service_tracking, self.service_policy, self.project_id, self.project_template_id) if vals: self.update(vals) def unlink(self): time_product = self.env.ref('sale_timesheet.time_product') if time_product.product_tmpl_id in self: raise ValidationError(_('The %s product is required by the Timesheet app and cannot be archived/deleted.') % time_product.name) return super(ProductTemplate, self).unlink() def write(self, vals): # timesheet product can't be archived test_mode = getattr(threading.currentThread(), 'testing', False) or self.env.registry.in_test_mode() if not test_mode and 'active' in vals and not vals['active']: time_product = self.env.ref('sale_timesheet.time_product') if time_product.product_tmpl_id in self: raise ValidationError(_('The %s product is required by the Timesheet app and cannot be archived/deleted.') % time_product.name) return super(ProductTemplate, self).write(vals) class ProductProduct(models.Model): _inherit = 'product.product' def _is_delivered_timesheet(self): """ Check if the product is a delivered timesheet """ self.ensure_one() return self.type == 'service' and self.service_policy == 'delivered_timesheet' @api.onchange('service_policy') def _onchange_service_policy(self): vals = self.product_tmpl_id._get_onchange_service_policy_updates(self.service_tracking, self.service_policy, self.project_id, self.project_template_id) if vals: self.update(vals) def unlink(self): time_product = self.env.ref('sale_timesheet.time_product') if time_product in self: raise ValidationError(_('The %s product is required by the Timesheet app and cannot be archived/deleted.') % time_product.name) return super(ProductProduct, self).unlink() def write(self, vals): # timesheet product can't be archived test_mode = getattr(threading.currentThread(), 'testing', False) or self.env.registry.in_test_mode() if not test_mode and 'active' in vals and not vals['active']: time_product = self.env.ref('sale_timesheet.time_product') if time_product in self: raise ValidationError(_('The %s product is required by the Timesheet app and cannot be archived/deleted.') % time_product.name) return super(ProductProduct, self).write(vals)