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:
parent
32def36af0
commit
87f9d59dc0
4 changed files with 92 additions and 3 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue