bemade_fsm 15.0.1.0.2 - add visits to FSM orders when missing

All sale orders containing a line that will produce an FSM task,
i.e. lines that are linked to a product of type service and with
service_tracking set to task_global_project, will be processed
to make sure that all such lines fall under a service visit. If
a visit already exists on the SO but lines are not covered by it,
the visit will be promoted to be the top item on the order before
confirmation. Otherwise, a default visit will be added to the top
of the order prior to proceeding with confirmation.
This commit is contained in:
Marc Durepos 2023-11-23 16:18:42 -05:00
parent 32def36af0
commit 87f9d59dc0
4 changed files with 92 additions and 3 deletions

View file

@ -20,7 +20,7 @@
########################################################################################
{
'name': 'Improved Field Service Management',
'version': '15.0.1.0.1',
'version': '15.0.1.0.2',
'summary': 'Adds functionality necessary for managing field service operations at Durpro.',
'description': 'Adds functionality necessary for managing field service operations at Durpro.',
'category': 'Services/Field Service',

View file

@ -50,6 +50,12 @@ class SaleOrder(models.Model):
readonly=False
)
is_fsm = fields.Boolean(
compute='_compute_is_fsm',
string='Is FSM',
store=True,
)
@api.depends('order_line.task_id')
def get_relevant_order_lines(self, task_id):
self.ensure_one()
@ -101,3 +107,42 @@ class SaleOrder(models.Model):
rec = super().copy(default)
rec.visit_ids = [Command.set(rec.order_line.visit_ids.ids)]
return rec
def _create_default_visit(self):
""" Called when an order is confirmed with lines that will create an FSM task, in order to make sure there is
a visit line grouping all the service being done."""
self.ensure_one()
visit = self.env['bemade_fsm.visit'].create({
'label': _('Service Visit'),
'sale_order_id': self.id,
})
# Make sure it goes to the top of the list
visit.so_section_id.sequence = 0
def _create_or_organize_visits_if_needed(self):
""" Adds a visit line to the top of the order if there are not already visit lines for an order with lines that
will create an FSM task."""
for order in self:
if not order.visit_ids and order.is_fsm:
order._create_default_visit()
if order.is_fsm:
# Make sure that all the lines producing FSM tasks are under a visit
visit_line_ids = order.mapped('visit_ids').mapped('so_section_id').mapped('section_line_ids')
if any([
True for line in
order.order_line.filtered(lambda line: not line.display_type)
if line not in visit_line_ids
]):
# If not, promote the first visit to the top of the order items list
for line in order.order_line:
line.sequence += 1
order.mapped('visit_ids').mapped('so_section_id')[0].sequence = 0
@api.depends('order_line.is_fsm')
def _compute_is_fsm(self):
for rec in self:
rec.is_fsm = any([line.is_fsm for line in rec.order_line])
def action_confirm(self):
self._create_or_organize_visits_if_needed()
return super().action_confirm()

View file

@ -57,6 +57,17 @@ class SaleOrderLine(models.Model):
compute="_compute_task_duration"
)
is_fsm = fields.Boolean(
string='Is FSM',
compute='_compute_is_fsm',
store=True,
)
section_line_ids = fields.One2many(
comodel_name='sale.order.line',
compute='_compute_section_line_ids',
)
@api.depends('visit_ids')
def _compute_visit_id(self):
for rec in self:
@ -214,6 +225,14 @@ class SaleOrderLine(models.Model):
lines.append(line)
return self.env['sale.order.line'].union(*lines)
@api.depends('display_type', 'order_id.order_line')
def _compute_section_line_ids(self):
for rec in self:
if rec.display_type == 'line_section':
rec.section_line_ids = [Command.set(rec.get_section_line_ids().ids)]
else:
rec.section_line_ids = False
def _iterate_items_compute_bool(self, single_line_func):
if not self.display_type:
return single_line_func(self)
@ -251,8 +270,7 @@ class SaleOrderLine(models.Model):
uom_hour = self.env.ref('uom.product_uom_hour')
uom_unit = self.env.ref('uom.product_uom_unit')
templated_lines = self.filtered(lambda l: l.product_id.task_template_id
and l.product_id.task_template_id.
planned_hours)
and l.product_id.task_template_id.planned_hours)
visit_lines = self.filtered(lambda l: l.visit_id)
regular_lines = self - templated_lines - visit_lines
for line in regular_lines:
@ -272,3 +290,9 @@ class SaleOrderLine(models.Model):
for line in visit_lines:
line.task_duration = sum(line.get_section_line_ids().
mapped('task_duration'))
@api.depends('product_id.detailed_type', 'product_id.service_tracking')
def _compute_is_fsm(self):
for rec in self:
rec.is_fsm = (rec.product_id.detailed_type == 'service'
and rec.product_id.service_tracking == 'task_global_project')

View file

@ -300,3 +300,23 @@ class TestSalesOrder(BemadeFSMBaseTest):
self.assertEqual(so2.order_line[0].visit_id, visit2)
self.assertEqual(visit2.label, visit.label)
self.assertFalse(visit2.approx_date)
def test_confirming_sale_order_creates_visit_if_none_created(self):
so = self._generate_sale_order()
product = self._generate_product()
sol = self._generate_sale_order_line(so, product)
so.action_confirm()
visit_line = so.order_line.sorted('sequence')[0]
self.assertTrue(so.visit_ids)
self.assertEqual(visit_line.visit_id, so.visit_ids)
def test_confirming_sale_order_with_visit_creates_no_new_lines(self):
so = self._generate_sale_order()
product = self._generate_product()
visit = self._generate_visit(so)
so.action_confirm()
self.assertEqual(len(so.visit_ids), 1)