bemade_fsm: manually merge in 15.0 differences and fix 2 failing tests
This commit is contained in:
parent
2a0ca65382
commit
149a30f6f3
12 changed files with 246 additions and 66 deletions
|
|
@ -8,4 +8,5 @@ from . import equipment_tag
|
||||||
from . import task
|
from . import task
|
||||||
from . import res_partner
|
from . import res_partner
|
||||||
from . import fsm_visit
|
from . import fsm_visit
|
||||||
from . import res_company
|
from . import res_company
|
||||||
|
from . import project
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ class FSMVisit(models.Model):
|
||||||
_name = "bemade_fsm.visit"
|
_name = "bemade_fsm.visit"
|
||||||
_description = 'Represents a single visit by assigned service personnel.'
|
_description = 'Represents a single visit by assigned service personnel.'
|
||||||
|
|
||||||
label = fields.Text(string="Label", required=True, related='so_section_id.name', readonly=False)
|
label = fields.Text(string="Label", required=True, related='so_section_id.name', readonly=False, copy=True)
|
||||||
|
|
||||||
approx_date = fields.Date(string='Approximate Date')
|
approx_date = fields.Date(string='Approximate Date', copy=False)
|
||||||
|
|
||||||
so_section_id = fields.Many2one(
|
so_section_id = fields.Many2one(
|
||||||
comodel_name="sale.order.line",
|
comodel_name="sale.order.line",
|
||||||
|
|
@ -19,7 +19,7 @@ class FSMVisit(models.Model):
|
||||||
sale_order_id = fields.Many2one(
|
sale_order_id = fields.Many2one(
|
||||||
comodel_name="sale.order",
|
comodel_name="sale.order",
|
||||||
string="Sales Order",
|
string="Sales Order",
|
||||||
required=True
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
is_completed = fields.Boolean(string="Completed", related="so_section_id.is_fully_delivered")
|
is_completed = fields.Boolean(string="Completed", related="so_section_id.is_fully_delivered")
|
||||||
|
|
@ -61,10 +61,10 @@ class FSMVisit(models.Model):
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
recs = super().create(vals_list)
|
recs = super().create(vals_list)
|
||||||
for i, rec in enumerate(recs):
|
for i, rec in enumerate(recs.filtered(lambda visit: not visit.so_section_id)):
|
||||||
rec.so_section_id = rec.env['sale.order.line'].create({
|
rec.so_section_id = rec.env['sale.order.line'].create({
|
||||||
'order_id': rec.sale_order_id.id,
|
'order_id': rec.sale_order_id.id,
|
||||||
'display_type': 'line_section',
|
'display_type': 'line_section',
|
||||||
'name': vals_list[i]['label'],
|
'name': vals_list[i].get('label', False),
|
||||||
})
|
})
|
||||||
return recs
|
return recs
|
||||||
|
|
|
||||||
11
bemade_fsm/models/project.py
Normal file
11
bemade_fsm/models/project.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
class Project(models.Model):
|
||||||
|
_inherit = "project.project"
|
||||||
|
|
||||||
|
def _fetch_sale_order_item_ids(self, domain_per_model=None, limit=None, offset=None):
|
||||||
|
# Override to flush the ORM cache to the database prior to running the query
|
||||||
|
# Temporary fix until Odoo fixes this method (PR #160067 submitted for this)
|
||||||
|
self.env.flush_all()
|
||||||
|
return super()._fetch_sale_order_item_ids(domain_per_model, limit, offset)
|
||||||
|
|
@ -50,11 +50,17 @@ class SaleOrder(models.Model):
|
||||||
readonly=False
|
readonly=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is_fsm = fields.Boolean(
|
||||||
|
compute='_compute_is_fsm',
|
||||||
|
string='Is FSM',
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('order_line.task_id')
|
@api.depends('order_line.task_id')
|
||||||
def get_relevant_order_lines(self, task_id):
|
def get_relevant_order_lines(self, task_id):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
linked_lines = self.order_line.filtered(lambda l: l.task_id == task_id
|
linked_lines = self.order_line.filtered(lambda l: l.task_id == task_id
|
||||||
or l == task_id.visit_id.so_section_id)
|
or l == task_id.visit_id.so_section_id)
|
||||||
visit_lines = linked_lines.filtered(lambda l: l.visit_id)
|
visit_lines = linked_lines.filtered(lambda l: l.visit_id)
|
||||||
for line in visit_lines:
|
for line in visit_lines:
|
||||||
linked_lines |= line.get_section_line_ids()
|
linked_lines |= line.get_section_line_ids()
|
||||||
|
|
@ -102,3 +108,41 @@ class SaleOrder(models.Model):
|
||||||
rec.visit_ids = [Command.set(rec.order_line.visit_ids.ids)]
|
rec.visit_ids = [Command.set(rec.order_line.visit_ids.ids)]
|
||||||
return rec
|
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.filtered("company_id.create_default_fsm_visit"):
|
||||||
|
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()
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ class SaleOrderLine(models.Model):
|
||||||
visit_ids = fields.One2many(
|
visit_ids = fields.One2many(
|
||||||
comodel_name="bemade_fsm.visit",
|
comodel_name="bemade_fsm.visit",
|
||||||
inverse_name="so_section_id",
|
inverse_name="so_section_id",
|
||||||
string="Visits"
|
string="Visits",
|
||||||
|
copy=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
visit_id = fields.Many2one(
|
visit_id = fields.Many2one(
|
||||||
|
|
@ -22,7 +23,7 @@ class SaleOrderLine(models.Model):
|
||||||
compute="_compute_visit_id",
|
compute="_compute_visit_id",
|
||||||
string="Visit",
|
string="Visit",
|
||||||
ondelete='cascade',
|
ondelete='cascade',
|
||||||
store=True
|
store=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
is_fully_delivered = fields.Boolean(
|
is_fully_delivered = fields.Boolean(
|
||||||
|
|
@ -56,6 +57,17 @@ class SaleOrderLine(models.Model):
|
||||||
compute="_compute_task_duration"
|
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')
|
@api.depends('visit_ids')
|
||||||
def _compute_visit_id(self):
|
def _compute_visit_id(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
|
@ -74,6 +86,13 @@ class SaleOrderLine(models.Model):
|
||||||
rec.equipment_ids = rec.order_id.default_equipment_ids
|
rec.equipment_ids = rec.order_id.default_equipment_ids
|
||||||
return recs
|
return recs
|
||||||
|
|
||||||
|
def copy_data(self, default=None):
|
||||||
|
if default is None:
|
||||||
|
default = {}
|
||||||
|
if 'visit_ids' not in default:
|
||||||
|
default['visit_ids'] = [(0, 0, visit.copy_data()[0]) for visit in self.visit_ids]
|
||||||
|
return super().copy_data(default)
|
||||||
|
|
||||||
def _timesheet_create_task(self, project):
|
def _timesheet_create_task(self, project):
|
||||||
""" Generate task for the given so line, and link it.
|
""" Generate task for the given so line, and link it.
|
||||||
:param project: record of project.project in which the task should be created
|
:param project: record of project.project in which the task should be created
|
||||||
|
|
@ -140,7 +159,7 @@ class SaleOrderLine(models.Model):
|
||||||
def _timesheet_service_generation(self):
|
def _timesheet_service_generation(self):
|
||||||
super()._timesheet_service_generation()
|
super()._timesheet_service_generation()
|
||||||
visit_lines = self.filtered(lambda l: l.visit_id)
|
visit_lines = self.filtered(lambda l: l.visit_id)
|
||||||
for line in visit_lines:
|
for index, line in enumerate(visit_lines):
|
||||||
task_ids = line.get_section_line_ids().mapped('task_id')
|
task_ids = line.get_section_line_ids().mapped('task_id')
|
||||||
if not task_ids:
|
if not task_ids:
|
||||||
continue
|
continue
|
||||||
|
|
@ -148,16 +167,15 @@ class SaleOrderLine(models.Model):
|
||||||
# Can't group up the tasks if they're part of different projects
|
# Can't group up the tasks if they're part of different projects
|
||||||
return
|
return
|
||||||
project_id = task_ids[0].project_id
|
project_id = task_ids[0].project_id
|
||||||
line.visit_id.task_id = line._generate_task_for_visit_line(project_id)
|
line.visit_id.task_id = line._generate_task_for_visit_line(project_id, index + 1)
|
||||||
task_ids.write({'parent_id': line.visit_id.task_id.id})
|
task_ids.write({'parent_id': line.visit_id.task_id.id})
|
||||||
self.task_id.filtered("is_fsm").synchronize_name_fsm()
|
(self.mapped('task_id') | self.visit_ids.task_id).filtered("is_fsm").synchronize_name_fsm()
|
||||||
|
|
||||||
def _generate_task_for_visit_line(self, project):
|
def _generate_task_for_visit_line(self, project, visit_no: int):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|
||||||
task = self.env['project.task'].create({
|
task = self.env['project.task'].create({
|
||||||
'name': 'Temp Name',
|
'name': f"{self.order_id.name} - " + _("Visit") + f" {visit_no} - {self.name}",
|
||||||
'description': f"Parent task for {self.order_id.name}, visit {self.name}",
|
|
||||||
'project_id': project.id,
|
'project_id': project.id,
|
||||||
'equipment_ids': self.get_section_line_ids().mapped('equipment_ids').ids,
|
'equipment_ids': self.get_section_line_ids().mapped('equipment_ids').ids,
|
||||||
'sale_order_id': self.order_id.id,
|
'sale_order_id': self.order_id.id,
|
||||||
|
|
@ -207,6 +225,14 @@ class SaleOrderLine(models.Model):
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
return self.env['sale.order.line'].union(*lines)
|
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):
|
def _iterate_items_compute_bool(self, single_line_func):
|
||||||
if not self.display_type:
|
if not self.display_type:
|
||||||
return single_line_func(self)
|
return single_line_func(self)
|
||||||
|
|
@ -244,8 +270,7 @@ class SaleOrderLine(models.Model):
|
||||||
uom_hour = self.env.ref('uom.product_uom_hour')
|
uom_hour = self.env.ref('uom.product_uom_hour')
|
||||||
uom_unit = self.env.ref('uom.product_uom_unit')
|
uom_unit = self.env.ref('uom.product_uom_unit')
|
||||||
templated_lines = self.filtered(lambda l: l.product_id.task_template_id
|
templated_lines = self.filtered(lambda l: l.product_id.task_template_id
|
||||||
and l.product_id.task_template_id.
|
and l.product_id.task_template_id.planned_hours)
|
||||||
planned_hours)
|
|
||||||
visit_lines = self.filtered(lambda l: l.visit_id)
|
visit_lines = self.filtered(lambda l: l.visit_id)
|
||||||
regular_lines = self - templated_lines - visit_lines
|
regular_lines = self - templated_lines - visit_lines
|
||||||
for line in regular_lines:
|
for line in regular_lines:
|
||||||
|
|
@ -265,3 +290,9 @@ class SaleOrderLine(models.Model):
|
||||||
for line in visit_lines:
|
for line in visit_lines:
|
||||||
line.task_duration = sum(line.get_section_line_ids().
|
line.task_duration = sum(line.get_section_line_ids().
|
||||||
mapped('task_duration'))
|
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')
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,9 @@ class Task(models.Model):
|
||||||
prev_seqs = self.sale_order_id.tasks_ids and \
|
prev_seqs = self.sale_order_id.tasks_ids and \
|
||||||
self.sale_order_id.tasks_ids.mapped('work_order_number')
|
self.sale_order_id.tasks_ids.mapped('work_order_number')
|
||||||
if prev_seqs:
|
if prev_seqs:
|
||||||
pattern = re.compile(r"\d+$")
|
pattern = re.compile(r"(\d+)$")
|
||||||
matches = map(lambda n: pattern.search(n), prev_seqs)
|
matches = map(lambda n: pattern.search(n), prev_seqs)
|
||||||
seq += max(map(lambda n: int(n.group(1)) if n else 0), matches)
|
seq += max(map(lambda n: int(n.group(1)) if n else 0, matches))
|
||||||
rec.work_order_number = rec.sale_order_id.name.replace('SO', 'SVR', 1) \
|
rec.work_order_number = rec.sale_order_id.name.replace('SO', 'SVR', 1) \
|
||||||
+ f"-{seq}"
|
+ f"-{seq}"
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
<data>
|
<data>
|
||||||
<template id="workorder_page_materials_table">
|
<template id="workorder_page_materials_table">
|
||||||
<t t-set="order_lines"
|
<t t-set="order_lines"
|
||||||
t-value="doc.relevant_order_lines"/>
|
t-value="doc.relevant_order_lines.filtered(lambda l:
|
||||||
<!--.filtered(lambda l:
|
l.product_id.type != 'service' and not l.is_downpayment and not l.visit_id)"/>
|
||||||
l.product_id.type != 'service' and not l.is_downpayment and not l.visit_id)"/>-->
|
|
||||||
<t t-if="order_lines">
|
<t t-if="order_lines">
|
||||||
<t t-set="visit_lines" t-value="order_lines.mapped('visit_id')"/>
|
<t t-set="visit_lines" t-value="order_lines.mapped('visit_id')"/>
|
||||||
<t t-set="section_lines"
|
<t t-set="section_lines"
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,6 @@ from odoo import Command
|
||||||
|
|
||||||
@tagged("-at_install", "post_install")
|
@tagged("-at_install", "post_install")
|
||||||
class BemadeFSMBaseTest(TransactionCase):
|
class BemadeFSMBaseTest(TransactionCase):
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
super().setUpClass()
|
|
||||||
cls.env.user.groups_id += cls.env.ref("account.group_delivery_invoice_address")
|
|
||||||
cls.env.company.create_default_fsm_visit = True
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_project_manager_user(cls, name, login):
|
def _generate_project_manager_user(cls, name, login):
|
||||||
|
|
@ -43,7 +37,6 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
user_group_fsm_user = cls.env.ref('industry_fsm.group_fsm_user')
|
user_group_fsm_user = cls.env.ref('industry_fsm.group_fsm_user')
|
||||||
user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman')
|
user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman')
|
||||||
user_group_sales_manager = cls.env.ref('sales_team.group_sale_manager')
|
user_group_sales_manager = cls.env.ref('sales_team.group_sale_manager')
|
||||||
# TODO: Split this out into a bemade_fsm_customer_product_code module if it's wanted
|
|
||||||
user_product_customer = cls.env.ref(
|
user_product_customer = cls.env.ref(
|
||||||
'customer_product_code.group_product_customer_code_user',
|
'customer_product_code.group_product_customer_code_user',
|
||||||
raise_if_not_found=False
|
raise_if_not_found=False
|
||||||
|
|
@ -79,11 +72,8 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_sale_order(cls, partner=None, client_order_ref='Test Order', equipment=None, shipping_location=None):
|
def _generate_sale_order(cls, partner=None, client_order_ref='Test Order', equipment=None, shipping_location=None):
|
||||||
partner = partner or cls._generate_partner()
|
partner = partner or cls._generate_partner()
|
||||||
vals = {
|
vals = {'partner_id': partner.id,
|
||||||
'partner_id': partner.id,
|
'client_order_ref': client_order_ref}
|
||||||
'client_order_ref': client_order_ref,
|
|
||||||
'payment_term_id': cls.env.ref('account.account_payment_term_immediate').id,
|
|
||||||
}
|
|
||||||
if equipment:
|
if equipment:
|
||||||
vals.update({'default_equipment_ids': [Command.set([equipment.id])]})
|
vals.update({'default_equipment_ids': [Command.set([equipment.id])]})
|
||||||
if shipping_location:
|
if shipping_location:
|
||||||
|
|
@ -110,22 +100,6 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
'partner_location_id': partner and partner.id or False,
|
'partner_location_id': partner and partner.id or False,
|
||||||
})
|
})
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _generate_product_category(cls):
|
|
||||||
company_id = cls.env.company.id
|
|
||||||
return cls.env['product.category'].create({
|
|
||||||
'name': 'Test Category',
|
|
||||||
'parent_id': cls.env.ref('product.product_category_all', False).id or False,
|
|
||||||
'property_account_income_categ_id': cls.env['account.account'].search(
|
|
||||||
[('account_type', '=', 'income'), ('company_id', '=', company_id)],
|
|
||||||
limit=1
|
|
||||||
).id,
|
|
||||||
'property_account_expense_categ_id': cls.env['account.account'].search(
|
|
||||||
[('account_type', '=', 'expense_direct_cost'), ('company_id', '=', company_id)],
|
|
||||||
limit=1
|
|
||||||
).id,
|
|
||||||
})
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_product(cls, name='Test Product', product_type='service', service_tracking='task_global_project',
|
def _generate_product(cls, name='Test Product', product_type='service', service_tracking='task_global_project',
|
||||||
project=None, task_template=None, service_policy='delivered_manual', uom=None):
|
project=None, task_template=None, service_policy='delivered_manual', uom=None):
|
||||||
|
|
@ -142,7 +116,6 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
'service_policy': service_policy,
|
'service_policy': service_policy,
|
||||||
'uom_id': uom_id,
|
'uom_id': uom_id,
|
||||||
'uom_po_id': uom_id,
|
'uom_po_id': uom_id,
|
||||||
'categ_id': cls._generate_product_category().id,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -201,7 +174,7 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
|
|
||||||
def _invoice_sale_order(self, so):
|
def _invoice_sale_order(self, so):
|
||||||
wiz = self.env['sale.advance.payment.inv'].with_context(
|
wiz = self.env['sale.advance.payment.inv'].with_context(
|
||||||
{'active_ids': [so.id]}).create([{}])
|
{'active_ids': [so.id]}).create({})
|
||||||
wiz.create_invoices()
|
wiz.create_invoices()
|
||||||
inv = so.invoice_ids[-1]
|
inv = so.invoice_ids[-1]
|
||||||
inv.action_post()
|
inv.action_post()
|
||||||
|
|
@ -222,3 +195,16 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
sol1.sequence = 2
|
sol1.sequence = 2
|
||||||
sol2.sequence = 3
|
sol2.sequence = 3
|
||||||
return so, visit, sol1, sol2
|
return so, visit, sol1, sol2
|
||||||
|
|
||||||
|
def _generate_so_with_one_visit_two_lines_and_descendants(self):
|
||||||
|
so = self._generate_sale_order()
|
||||||
|
visit = self._generate_visit(sale_order=so)
|
||||||
|
task_template = self._generate_task_template(structure=[2, 2, 2],
|
||||||
|
names=['Parent', 'Child', 'Grandchild', 'Great-grandchild'])
|
||||||
|
product = self._generate_product(task_template=task_template)
|
||||||
|
sol1 = self._generate_sale_order_line(sale_order=so, product=product)
|
||||||
|
sol2 = self._generate_sale_order_line(sale_order=so, product=product)
|
||||||
|
visit.so_section_id.sequence = 1
|
||||||
|
sol1.sequence = 2
|
||||||
|
sol2.sequence = 3
|
||||||
|
return so, visit, sol1, sol2
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,7 @@ class TestEquipment(BemadeFSMBaseTest):
|
||||||
partner_company.write({'equipment_ids': [Command.set([])]})
|
partner_company.write({'equipment_ids': [Command.set([])]})
|
||||||
with self.assertRaises(MissingError):
|
with self.assertRaises(MissingError):
|
||||||
equipment.name
|
equipment.name
|
||||||
|
|
||||||
|
def test_compute_complete_name_when_name_blank(self):
|
||||||
|
equipment = self._generate_equipment(name=False)
|
||||||
|
complete_name = equipment.complete_name
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
||||||
so.action_confirm()
|
so.action_confirm()
|
||||||
task = so.order_line.filtered(lambda l: l.task_id).task_id
|
task = so.order_line.filtered(lambda l: l.task_id).task_id
|
||||||
|
|
||||||
task.action_fsm_validate(stop_running_timers=True)
|
task.action_fsm_validate()
|
||||||
|
|
||||||
self.assertTrue(visit.is_completed)
|
self.assertTrue(visit.is_completed)
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
||||||
self._generate_sale_order_line(so)
|
self._generate_sale_order_line(so)
|
||||||
so.action_confirm()
|
so.action_confirm()
|
||||||
task = so.order_line.filtered(lambda l: l.task_id).task_id
|
task = so.order_line.filtered(lambda l: l.task_id).task_id
|
||||||
task.action_fsm_validate(stop_running_timers=True)
|
task.action_fsm_validate()
|
||||||
|
|
||||||
self._invoice_sale_order(so)
|
self._invoice_sale_order(so)
|
||||||
|
|
||||||
|
|
@ -102,23 +102,34 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
||||||
self.assertEqual(len(so.order_line), 3)
|
self.assertEqual(len(so.order_line), 3)
|
||||||
|
|
||||||
def test_marking_visit_task_done_completes_descendants(self):
|
def test_marking_visit_task_done_completes_descendants(self):
|
||||||
so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines()
|
so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines_and_descendants()
|
||||||
so.action_confirm()
|
so.action_confirm()
|
||||||
parent, child1, child2 = visit.task_id, sol1.task_id, sol2.task_id
|
parent = visit.task_id
|
||||||
|
|
||||||
parent.action_fsm_validate(stop_running_timers=True)
|
parent.action_fsm_validate()
|
||||||
|
|
||||||
self.assertTrue(parent.is_closed)
|
self._assert_is_done(parent)
|
||||||
self.assertTrue(child1.is_closed)
|
|
||||||
self.assertTrue(child2.is_closed)
|
def _assert_is_done(self, task):
|
||||||
self.assertEqual(sol1.qty_to_deliver, 0)
|
""" Recursively assert all tasks in a hierarchy are complete """
|
||||||
self.assertEqual(sol2.qty_to_deliver, 0)
|
self.assertTrue(task.is_closed)
|
||||||
self.assertTrue(visit.is_completed)
|
for child in task.child_ids:
|
||||||
|
self._assert_is_done(child)
|
||||||
|
|
||||||
def test_marking_visit_task_done_does_not_create_sale_order_line(self):
|
def test_marking_visit_task_done_does_not_create_sale_order_line(self):
|
||||||
so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines()
|
so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines()
|
||||||
so.action_confirm()
|
so.action_confirm()
|
||||||
|
|
||||||
visit.task_id.action_fsm_validate(stop_running_timers=True)
|
visit.task_id.action_fsm_validate()
|
||||||
|
|
||||||
self.assertEqual(len(so.order_line), 3)
|
self.assertEqual(len(so.order_line), 3)
|
||||||
|
|
||||||
|
def test_confirming_so_names_visit_properly(self):
|
||||||
|
""" Visits should be named <SO NUMBER> - Visit <visit #> - <visit label>"""
|
||||||
|
so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines()
|
||||||
|
|
||||||
|
so.action_confirm()
|
||||||
|
task = visit.task_id
|
||||||
|
|
||||||
|
supposed_name = f"{so.name} - Visit 1 - {visit.label}"
|
||||||
|
self.assertEqual(task.name, supposed_name)
|
||||||
|
|
|
||||||
|
|
@ -183,11 +183,11 @@ class TestSalesOrder(BemadeFSMBaseTest):
|
||||||
subtasks = parent_task._get_all_subtasks()
|
subtasks = parent_task._get_all_subtasks()
|
||||||
|
|
||||||
# Marking the subtasks done should not increment delivered quantity
|
# Marking the subtasks done should not increment delivered quantity
|
||||||
subtasks.action_fsm_validate()
|
subtasks.action_fsm_validate(True)
|
||||||
self.assertEqual(sol.qty_delivered, 0)
|
self.assertEqual(sol.qty_delivered, 0)
|
||||||
|
|
||||||
# Marking the top-level tasks done should set the delivered quantity to some non-zero value based on the UOM
|
# Marking the top-level tasks done should set the delivered quantity to some non-zero value based on the UOM
|
||||||
parent_task.action_fsm_validate()
|
parent_task.action_fsm_validate(True)
|
||||||
self.assertTrue(sol.qty_delivered != 0)
|
self.assertTrue(sol.qty_delivered != 0)
|
||||||
|
|
||||||
def test_task_contacts_through_sale_order(self):
|
def test_task_contacts_through_sale_order(self):
|
||||||
|
|
@ -287,3 +287,49 @@ class TestSalesOrder(BemadeFSMBaseTest):
|
||||||
self.assertEqual(task.child_ids[0].description, template.subtasks[0].description)
|
self.assertEqual(task.child_ids[0].description, template.subtasks[0].description)
|
||||||
for t in task.child_ids[1:]:
|
for t in task.child_ids[1:]:
|
||||||
self.assertFalse(t.description)
|
self.assertFalse(t.description)
|
||||||
|
|
||||||
|
def test_duplicate_sale_order_duplicates_visits(self):
|
||||||
|
""" Duplicated sales orders should have visits tied to their SO lines as in the original. The copied visits
|
||||||
|
should not have approximate dates set, however."""
|
||||||
|
so, visit, line1, line2 = self._generate_so_with_one_visit_two_lines()
|
||||||
|
|
||||||
|
so2 = so.copy()
|
||||||
|
|
||||||
|
self.assertTrue(so2.visit_ids)
|
||||||
|
visit2 = so2.visit_ids[0]
|
||||||
|
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()
|
||||||
|
so.company_id.create_default_fsm_visit = True
|
||||||
|
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()
|
||||||
|
so.company_id.create_default_fsm_visit = True
|
||||||
|
product = self._generate_product()
|
||||||
|
visit = self._generate_visit(so)
|
||||||
|
|
||||||
|
so.action_confirm()
|
||||||
|
|
||||||
|
self.assertEqual(len(so.visit_ids), 1)
|
||||||
|
|
||||||
|
def test_confirming_sale_order_creates_no_visit_if_setting_off(self):
|
||||||
|
so = self._generate_sale_order()
|
||||||
|
so.company_id.create_default_fsm_visit = False
|
||||||
|
product = self._generate_product()
|
||||||
|
sol = self._generate_sale_order_line(so, product)
|
||||||
|
|
||||||
|
so.action_confirm()
|
||||||
|
|
||||||
|
visit_line = so.order_line.sorted('sequence')[0]
|
||||||
|
self.assertFalse(so.visit_ids)
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,28 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="project_task_view_list_fsm_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">project.task.view.list.fsm.inherit</field>
|
||||||
|
<field name="model">project.task</field>
|
||||||
|
<field name="inherit_id" ref="industry_fsm.project_task_view_list_fsm"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree position="attributes">
|
||||||
|
<attribute name="js_class">project_list</attribute>
|
||||||
|
</tree>
|
||||||
|
<field name="partner_id" position="after">
|
||||||
|
<field name="work_order_number" optional="show"/>
|
||||||
|
</field>
|
||||||
|
<field name="company_id" position="attributes">
|
||||||
|
<attribute name="optional">hide</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="worksheet_template_id" position="attributes">
|
||||||
|
<attribute name="optional">hide</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="project_id" position="attributes">
|
||||||
|
<attribute name="optional">hide</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
<record id="industry_fsm.project_task_action_fsm_map"
|
<record id="industry_fsm.project_task_action_fsm_map"
|
||||||
model="ir.actions.act_window">
|
model="ir.actions.act_window">
|
||||||
<field name="domain">[('is_fsm', '=', True),
|
<field name="domain">[('is_fsm', '=', True),
|
||||||
|
|
@ -169,5 +191,30 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="project_task_view_search_fsm_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">project.task.view.search.fsm.inherit</field>
|
||||||
|
<field name="model">project.task</field>
|
||||||
|
<field name="inherit_id" ref="industry_fsm.project_task_view_search_fsm"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<filter name="schedule" position="attributes">
|
||||||
|
<attribute name="domain">
|
||||||
|
[
|
||||||
|
'&',
|
||||||
|
('fsm_done', '=', False),
|
||||||
|
'|',
|
||||||
|
('user_ids', '=', False),
|
||||||
|
'&',
|
||||||
|
('planned_date_start', '=', False),
|
||||||
|
('date_deadline', '=', False),
|
||||||
|
]
|
||||||
|
</attribute>
|
||||||
|
</filter>
|
||||||
|
<filter name="my_tasks" position="after">
|
||||||
|
<filter name="is_parent_task"
|
||||||
|
string="Parent Task"
|
||||||
|
domain="[('parent_id', '=', False)]"/>
|
||||||
|
</filter>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue