Odoo18-Base/addons/sale_timesheet/models/project_task.py
2025-01-06 10:57:38 +07:00

108 lines
5.3 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.osv import expression
class ProjectTask(models.Model):
_inherit = "project.task"
def _get_default_partner_id(self, project, parent):
res = super()._get_default_partner_id(project, parent)
if not res and project:
# project in sudo if the current user is a portal user.
related_project = project
if self.env.user._is_portal() and not self.env.user._is_internal():
related_project = related_project.sudo()
if related_project.pricing_type == 'employee_rate':
return related_project.sale_line_employee_ids.sale_line_id.order_partner_id[:1]
return res
sale_order_id = fields.Many2one(domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id.commercial_partner_id.id', 'parent_of', partner_id), ('partner_id', 'parent_of', partner_id)]")
pricing_type = fields.Selection(related="project_id.pricing_type")
is_project_map_empty = fields.Boolean("Is Project map empty", compute='_compute_is_project_map_empty')
has_multi_sol = fields.Boolean(compute='_compute_has_multi_sol', compute_sudo=True)
timesheet_product_id = fields.Many2one(related="project_id.timesheet_product_id")
remaining_hours_so = fields.Float('Time Remaining on SO', compute='_compute_remaining_hours_so', search='_search_remaining_hours_so', compute_sudo=True)
remaining_hours_available = fields.Boolean(related="sale_line_id.remaining_hours_available")
@property
def SELF_READABLE_FIELDS(self):
return super().SELF_READABLE_FIELDS | {
'remaining_hours_available',
'remaining_hours_so',
}
@api.depends('sale_line_id', 'timesheet_ids', 'timesheet_ids.unit_amount')
def _compute_remaining_hours_so(self):
# TODO This is not yet perfectly working as timesheet.so_line stick to its old value although changed
# in the task From View.
timesheets = self.timesheet_ids.filtered(lambda t: t.task_id.sale_line_id in (t.so_line, t._origin.so_line) and t.so_line.remaining_hours_available)
mapped_remaining_hours = {task._origin.id: task.sale_line_id and task.sale_line_id.remaining_hours or 0.0 for task in self}
uom_hour = self.env.ref('uom.product_uom_hour')
for timesheet in timesheets:
delta = 0
if timesheet._origin.so_line == timesheet.task_id.sale_line_id:
delta += timesheet._origin.unit_amount
if timesheet.so_line == timesheet.task_id.sale_line_id:
delta -= timesheet.unit_amount
if delta:
mapped_remaining_hours[timesheet.task_id._origin.id] += timesheet.product_uom_id._compute_quantity(delta, uom_hour)
for task in self:
task.remaining_hours_so = mapped_remaining_hours[task._origin.id]
@api.model
def _search_remaining_hours_so(self, operator, value):
return [('sale_line_id.remaining_hours', operator, value)]
def _inverse_partner_id(self):
super()._inverse_partner_id()
for task in self:
if task.allow_billable and not task.sale_line_id:
task.sale_line_id = task.sudo()._get_last_sol_of_customer()
@api.depends('sale_line_id.order_partner_id', 'parent_id.sale_line_id', 'project_id.sale_line_id', 'allow_billable')
def _compute_sale_line(self):
super()._compute_sale_line()
for task in self:
if task.allow_billable and not task.sale_line_id:
task.sale_line_id = task._get_last_sol_of_customer()
@api.depends('project_id.sale_line_employee_ids')
def _compute_is_project_map_empty(self):
for task in self:
task.is_project_map_empty = not bool(task.sudo().project_id.sale_line_employee_ids)
@api.depends('timesheet_ids')
def _compute_has_multi_sol(self):
for task in self:
task.has_multi_sol = task.timesheet_ids and task.timesheet_ids.so_line != task.sale_line_id
def _get_last_sol_of_customer(self):
# Get the last SOL made for the customer in the current task where we need to compute
self.ensure_one()
if not self.partner_id.commercial_partner_id or not self.allow_billable:
return False
SaleOrderLine = self.env['sale.order.line']
domain = expression.AND([
SaleOrderLine._domain_sale_line_service(),
[
('company_id', '=?', self.company_id.id),
('order_partner_id', 'child_of', self.partner_id.commercial_partner_id.id),
('remaining_hours', '>', 0),
],
])
if self.project_id.pricing_type != 'task_rate' and self.project_sale_order_id and self.partner_id.commercial_partner_id == self.project_id.partner_id.commercial_partner_id:
domain = expression.AND([domain, [('order_id', '=?', self.project_sale_order_id.id)]])
return SaleOrderLine.search(domain, limit=1)
def _get_timesheet(self):
# return not invoiced timesheet and timesheet without so_line or so_line linked to task
timesheet_ids = super()._get_timesheet()
return timesheet_ids.filtered(lambda t: t._is_not_billed())
def _get_action_view_so_ids(self):
return list(set((self.sale_order_id + self.timesheet_ids.so_line.order_id).ids))