product.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import threading
  4. from odoo import api, fields, models, _
  5. from odoo.exceptions import ValidationError
  6. class ProductTemplate(models.Model):
  7. _inherit = 'product.template'
  8. def _selection_service_policy(self):
  9. service_policies = super()._selection_service_policy()
  10. service_policies.insert(1, ('delivered_timesheet', _('Based on Timesheets')))
  11. return service_policies
  12. service_type = fields.Selection(selection_add=[
  13. ('timesheet', 'Timesheets on project (one fare per SO/Project)'),
  14. ], ondelete={'timesheet': 'set manual'})
  15. # override domain
  16. project_id = fields.Many2one(domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('pricing_type', '=', 'task_rate'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]")
  17. project_template_id = fields.Many2one(domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]")
  18. service_upsell_threshold = fields.Float('Threshold', default=1, help="Percentage of time delivered compared to the prepaid amount that must be reached for the upselling opportunity activity to be triggered.")
  19. service_upsell_threshold_ratio = fields.Char(compute='_compute_service_upsell_threshold_ratio')
  20. @api.depends('uom_id')
  21. def _compute_service_upsell_threshold_ratio(self):
  22. product_uom_hour = self.env.ref('uom.product_uom_hour')
  23. for record in self:
  24. if not record.uom_id or product_uom_hour.factor == record.uom_id.factor:
  25. record.service_upsell_threshold_ratio = False
  26. continue
  27. if product_uom_hour.factor != record.uom_id.factor:
  28. record.service_upsell_threshold_ratio = f"(1 {record.uom_id.name} = {product_uom_hour.factor / record.uom_id.factor:.2f} Hours)"
  29. def _compute_visible_expense_policy(self):
  30. visibility = self.user_has_groups('project.group_project_user')
  31. for product_template in self:
  32. if not product_template.visible_expense_policy:
  33. product_template.visible_expense_policy = visibility
  34. return super(ProductTemplate, self)._compute_visible_expense_policy()
  35. @api.depends('service_tracking', 'service_policy', 'type')
  36. def _compute_product_tooltip(self):
  37. super()._compute_product_tooltip()
  38. for record in self.filtered(lambda record: record.type == 'service'):
  39. if record.service_policy == 'delivered_timesheet':
  40. if record.service_tracking == 'no':
  41. record.product_tooltip = _(
  42. "Invoice based on timesheets (delivered quantity) on projects or tasks "
  43. "you'll create later on."
  44. )
  45. elif record.service_tracking == 'task_global_project':
  46. record.product_tooltip = _(
  47. "Invoice based on timesheets (delivered quantity), and create a task in "
  48. "an existing project to track the time spent."
  49. )
  50. elif record.service_tracking == 'task_in_project':
  51. record.product_tooltip = _(
  52. "Invoice based on timesheets (delivered quantity), and create a project "
  53. "for the order with a task for each sales order line to track the time "
  54. "spent."
  55. )
  56. elif record.service_tracking == 'project_only':
  57. record.product_tooltip = _(
  58. "Invoice based on timesheets (delivered quantity), and create an empty "
  59. "project for the order to track the time spent."
  60. )
  61. def _get_service_to_general_map(self):
  62. return {
  63. **super()._get_service_to_general_map(),
  64. 'delivered_timesheet': ('delivery', 'timesheet'),
  65. 'ordered_prepaid': ('order', 'timesheet'),
  66. }
  67. @api.model
  68. def _get_onchange_service_policy_updates(self, service_tracking, service_policy, project_id, project_template_id):
  69. vals = {}
  70. if service_tracking != 'no' and service_policy == 'delivered_timesheet':
  71. if project_id and not project_id.allow_timesheets:
  72. vals['project_id'] = False
  73. elif project_template_id and not project_template_id.allow_timesheets:
  74. vals['project_template_id'] = False
  75. return vals
  76. @api.onchange('service_policy')
  77. def _onchange_service_policy(self):
  78. self._inverse_service_policy()
  79. vals = self._get_onchange_service_policy_updates(self.service_tracking,
  80. self.service_policy,
  81. self.project_id,
  82. self.project_template_id)
  83. if vals:
  84. self.update(vals)
  85. @api.ondelete(at_uninstall=False)
  86. def _unlink_except_master_data(self):
  87. time_product = self.env.ref('sale_timesheet.time_product')
  88. if time_product.product_tmpl_id in self:
  89. raise ValidationError(_('The %s product is required by the Timesheets app and cannot be archived nor deleted.') % time_product.name)
  90. def write(self, vals):
  91. # timesheet product can't be archived
  92. test_mode = getattr(threading.current_thread(), 'testing', False) or self.env.registry.in_test_mode()
  93. if not test_mode and 'active' in vals and not vals['active']:
  94. time_product = self.env.ref('sale_timesheet.time_product')
  95. if time_product.product_tmpl_id in self:
  96. raise ValidationError(_('The %s product is required by the Timesheets app and cannot be archived nor deleted.') % time_product.name)
  97. return super(ProductTemplate, self).write(vals)
  98. class ProductProduct(models.Model):
  99. _inherit = 'product.product'
  100. def _is_delivered_timesheet(self):
  101. """ Check if the product is a delivered timesheet """
  102. self.ensure_one()
  103. return self.type == 'service' and self.service_policy == 'delivered_timesheet'
  104. @api.onchange('service_policy')
  105. def _onchange_service_policy(self):
  106. self._inverse_service_policy()
  107. vals = self.product_tmpl_id._get_onchange_service_policy_updates(self.service_tracking,
  108. self.service_policy,
  109. self.project_id,
  110. self.project_template_id)
  111. if vals:
  112. self.update(vals)
  113. @api.ondelete(at_uninstall=False)
  114. def _unlink_except_master_data(self):
  115. time_product = self.env.ref('sale_timesheet.time_product')
  116. if time_product in self:
  117. raise ValidationError(_('The %s product is required by the Timesheets app and cannot be archived nor deleted.') % time_product.name)
  118. def write(self, vals):
  119. # timesheet product can't be archived
  120. test_mode = getattr(threading.current_thread(), 'testing', False) or self.env.registry.in_test_mode()
  121. if not test_mode and 'active' in vals and not vals['active']:
  122. time_product = self.env.ref('sale_timesheet.time_product')
  123. if time_product in self:
  124. raise ValidationError(_('The %s product is required by the Timesheets app and cannot be archived nor deleted.') % time_product.name)
  125. return super(ProductProduct, self).write(vals)