bemade_fsm: Visits on sales orders now group created tasks under a common parent. Fixes #72.

This commit is contained in:
Marc Durepos 2023-07-20 11:09:10 -04:00
parent 454656159c
commit 37aea5c181
4 changed files with 68 additions and 20 deletions

View file

@ -24,6 +24,16 @@ class FSMVisit(models.Model):
summarized_equipment_ids = fields.Many2many(comodel_name="bemade_fsm.equipment",
string="Equipment to Service",
compute="_compute_summarized_equipment_ids")
task_id = fields.Many2one(comodel_name="project.task",
compute="_compute_task_id",
string="Service Visit",)
task_ids = fields.One2many(comodel_name="project.task",
inverse_name="visit_id")
@api.depends('task_ids')
def _compute_task_id(self):
for rec in self:
rec.task_id = rec.task_ids and rec.task_ids[0]
@api.depends('so_section_id', 'sale_order_id.summary_equipment_ids')
def _compute_summarized_equipment_ids(self):

View file

@ -89,6 +89,7 @@ class SaleOrderLine(models.Model):
column1="sale_order_line_id",
column2="equipment_id")
@api.model_create_multi
def create(self, vals):
recs = super().create(vals)
@ -162,6 +163,33 @@ class SaleOrderLine(models.Model):
task.name = _generate_task_name(tmpl)
return task
def _timesheet_service_generation(self):
super()._timesheet_service_generation()
visit_lines = self.filtered(lambda l: l.visit_id)
for line in visit_lines:
task_ids = line.get_section_lines().mapped('task_id')
if not task_ids:
continue
if len(set([task.project_id for task in task_ids])) > 1:
# Can't group up the tasks if they're part of different projects
return
project_id = task_ids[0].project_id
line.visit_id.task_id = line._generate_task_for_visit_line(project_id)
task_ids.write({'parent_id': line.visit_id.task_id.id})
def _generate_task_for_visit_line(self, project):
self.ensure_one()
task = self.env['project.task'].create({
'name': self.order_id.name + ": " + self.name,
'description': f"Parent task for {self.order_id.name}, visit {self.name}",
'project_id': project.id,
'equipment_ids': self.get_section_lines().mapped('equipment_ids').ids,
'sale_order_id': self.order_id.id,
'partner_id': self.order_id.partner_shipping_id.id,
'visit_id': self.visit_id.id,
})
return task
@api.depends('order_id.order_line', 'display_type', 'qty_to_deliver', 'order_id.order_line.qty_to_deliver',
'order_id.order_line.display_type')
def _compute_is_fully_delivered(self):
@ -175,22 +203,22 @@ class SaleOrderLine(models.Model):
self.is_fully_delivered_and_invoiced = self._iterate_items_compute_bool(lambda l: l.qty_to_invoice == 0)
def get_section_lines(self):
""" Returns a list containing the sale order lines that fall under this section. """
""" Returns a RecordSet containing the sale order lines that fall under this section. """
self.ensure_one()
assert self.display_type == 'line_section', 'Method called incorrectly on non-section order line.'
found = False
lines = []
for line in self.order_id:
for line in self.order_id.order_line:
if line == self:
found = True
continue
if not found:
continue
if line.display_type == 'line_section': # Stop when we hit the next section
return lines
break
else:
lines.add(line)
return lines
lines.append(line)
return self.env['sale.order.line'].union(*lines)
def _iterate_items_compute_bool(self, single_line_func):
if not self.display_type:

View file

@ -32,6 +32,8 @@ class Task(models.Model):
allow_billable = fields.Boolean(string="Can be billed",
related=False,
compute="_compute_allow_billable",)
visit_id = fields.Many2one(comodel_name='bemade_fsm.visit')
@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
@ -55,10 +57,11 @@ class Task(models.Model):
'site_contacts': [Command.set(rec.site_contacts.ids)],
})
@api.depends('parent_id', 'project_id')
@api.depends('parent_id.visit_id', 'project_id.is_fsm', 'project_id.allow_billable')
def _compute_allow_billable(self):
for rec in self:
if rec.parent_id and rec.project_id and rec.project_id.is_fsm:
# 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

View file

@ -7,7 +7,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
def test_create_visit_sets_name_on_section(self):
so = self._generate_sale_order()
self._add_service_so_line(so)
self._generate_sale_order_line(sale_order=so)
visit = self._generate_visit(so)
@ -35,7 +35,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
def test_visit_completes_when_task_completes(self):
so = self._generate_sale_order()
visit = self._generate_visit(so)
self._add_service_so_line(so, task=True)
self._generate_sale_order_line(so)
so.action_confirm()
task = so.order_line.filtered(lambda l: l.task_id).task_id
@ -46,7 +46,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
def test_visit_shows_invoiced_when_invoiced(self):
so = self._generate_sale_order()
visit = self._generate_visit(so)
self._add_service_so_line(so, task=True)
self._generate_sale_order_line(so)
so.action_confirm()
task = so.order_line.filtered(lambda l: l.task_id).task_id
task.action_fsm_validate()
@ -55,6 +55,23 @@ class FSMVisitTest(BemadeFSMBaseTest):
self.assertTrue(visit.is_invoiced)
def test_visit_groups_section_tasks_when_confirmed(self):
partner = self._generate_partner()
so = self._generate_sale_order()
visit = self._generate_visit(sale_order=so)
sol1 = self._generate_sale_order_line(sale_order=so)
sol2 = self._generate_sale_order_line(sale_order=so)
visit.so_section_id.sequence = 1
sol1.sequence = 2
sol2.sequence = 3
so.action_confirm()
visit_task = visit.task_id
self.assertTrue(visit_task)
visit_subtasks = visit_task.child_ids
self.assertTrue(visit_subtasks and sol1.task_id in visit_subtasks and sol2.task_id in visit_subtasks)
def _invoice_sale_order(self, so):
wiz = self.env['sale.advance.payment.inv'].with_context({'active_ids': [so.id]}).create({})
wiz.create_invoices()
@ -67,13 +84,3 @@ class FSMVisitTest(BemadeFSMBaseTest):
'sale_order_id': sale_order.id,
'label': label,
}])
def _add_service_so_line(self, sale_order, task: bool = False):
""" Generates a sales order line for a service product.
:param sale_order: The sales order to which the new line is to be added
:param task: If true, the created line will be for a product with service_tracking=task_global_project
"""
service_tracking = 'task_global_project' if task else 'no'
product = self._generate_product(service_tracking=service_tracking)
return self._generate_sale_order_line(sale_order, product)