bemade-addons/bemade_fsm/models/task.py

120 lines
6.1 KiB
Python

from odoo import fields, models, api, Command, _
from odoo.exceptions import ValidationError, UserError
from odoo.osv import expression
from collections import defaultdict, namedtuple
class Task(models.Model):
_inherit = "project.task"
equipment_ids = fields.Many2many(comodel_name="bemade_fsm.equipment",
relation="bemade_fsm_task_equipment_rel",
column1="task_id",
column2="equipment_id",
string="Equipment to Service",
tracking=True, )
work_order_contacts = fields.Many2many(comodel_name="res.partner",
relation="task_work_order_contact_rel",
column1="task_id",
column2="res_partner_id",
compute="_compute_contacts",
inverse="_inverse_contacts",
store=True)
site_contacts = fields.Many2many(comodel_name="res.partner",
relation="task_site_contact_rel",
column1="task_id",
column2="res_partner_id",
compute="_compute_contacts",
inverse="_inverse_contacts",
store=True)
planned_date_begin = fields.Datetime("Start Date", tracking=True,
task_dependency_tracking=True,
compute="_compute_planned_dates",
inverse="_inverse_planned_dates",
store=True)
planned_date_end = fields.Datetime("End Date", tracking=True,
task_dependency_tracking=True,
compute="_compute_planned_dates",
inverse="_inverse_planned_dates",
store=True)
# Override related field to make it return false if this is an FSM subtask
allow_billable = fields.Boolean(string="Can be billed",
related=False,
compute="_compute_allow_billable", )
visit_id = fields.Many2one(comodel_name='bemade_fsm.visit')
def _get_related_planning_slots(self):
domain = expression.AND([
self._get_domain_compute_forecast_hours(),
[('task_id', 'in', self.ids + self._get_all_subtasks().ids)]
])
return self.env['planning.slot'].search(domain)
@api.depends('forecast_hours')
def _compute_planned_dates(self):
forecast_data = self._get_related_planning_slots()
mapped_data = {}
TimeSpan = namedtuple('timespan', ['start', 'end'])
for d in forecast_data:
if d not in mapped_data:
mapped_data.update({d: TimeSpan(d.start_datetime, d.end_datetime)})
continue
if mapped_data[d].start > d.start_datetime:
mapped_data[d].start = d.start_datetime
if mapped_data[d].end < d.end_datetime:
mapped_data[d].end = d.end_datetime
for rec in self:
if rec not in mapped_data:
if not rec.planned_date_end:
rec.planned_date_end = False
if not rec.planned_date_begin:
rec.planned_date_begin = False
continue
rec.planned_date_begin, rec.planned_date_end = mapped_data[rec]
def _inverse_planned_dates(self):
""" Modifying the planned dates for tasks with existing planning records
(planning.slot) has no defined safe behaviour, so we block it."""
if self._get_related_planning_slots():
raise UserError(_("Modifying the planned start or end time on a task is not "
"permitted when that task already has planning records "
"associated to it. Please modify or delete the planning "
"records instead."))
@api.depends('sale_line_id.order_id.site_contacts',
'sale_line_id.order_id.work_order_contacts')
def _compute_contacts(self):
""" The work order contacts and site contacts for a given task are taken from the sale order if the task
is related to one, and from the task's customer if there is no sale order related to the task."""
for rec in self:
site_contacts = self.sale_line_id and self.sale_line_id.order_id.site_contacts or \
self.partner_id.site_contacts
work_order_contacts = self.sale_line_id and self.sale_line_id.order_id.work_order_contacts or \
self.partner_id.work_order_contacts
rec.write({
'site_contacts': [Command.set(site_contacts.ids)],
'work_order_contacts': [Command.set(work_order_contacts.ids)]
})
def _inverse_contacts(self):
""" If the task is linked to a sales order, the sales order should have its contacts updated to match."""
for rec in self:
if rec.sale_line_id:
rec.sale_line_id.order_id.write({
'work_order_contacts': [Command.set(rec.work_order_contacts.ids)],
'site_contacts': [Command.set(rec.site_contacts.ids)],
})
@api.depends('parent_id.visit_id', 'project_id.is_fsm', 'project_id.allow_billable')
def _compute_allow_billable(self):
for rec in self:
# If an FSM task has a parent that is linked to an SO line, then the parent is the billable one
if rec.parent_id and not rec.parent_id.visit_id and rec.project_id and rec.project_id.is_fsm:
rec.allow_billable = False
else:
rec.allow_billable = rec.project_id.allow_billable