multiple fixes for bemade_fsm to help pass tests.
This commit is contained in:
parent
5ea75d7bf9
commit
bf98462a09
19 changed files with 145 additions and 45 deletions
|
|
@ -55,6 +55,7 @@
|
|||
'reports/worksheet_custom_report_templates.xml',
|
||||
'reports/worksheet_custom_reports.xml',
|
||||
'wizard/new_task_from_template.xml',
|
||||
'wizard/res_config_settings.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.report_assets_common': [
|
||||
|
|
|
|||
|
|
@ -26,25 +26,5 @@
|
|||
<!-- <field name="is_closed" eval="False"/>-->
|
||||
<field name="project_ids" eval="[(4,ref('industry_fsm.fsm_project'))]"/>
|
||||
</record>
|
||||
<!-- Since the Field Service project has no_update="1" we use a workaround here -->
|
||||
<function model="ir.model.data" name="write">
|
||||
<function name="search" model="ir.model.data">
|
||||
<value eval="[('name', '=', 'fsm_project'), ('module', '=', 'industry_fsm'), ('model', '=', 'project.project')]"/>
|
||||
</function>
|
||||
<value eval="{'noupdate': False}"/>
|
||||
</function>
|
||||
<record id="industry_fsm.fsm_project" model="project.project">
|
||||
<field name="type_ids"
|
||||
eval="[(4, ref('industry_fsm.planning_project_stage_0')), (4, ref('industry_fsm.planning_project_stage_1')), (4, ref('industry_fsm.planning_project_stage_2')), (4, ref('planning_project_stage_work_completed')), (4, ref('industry_fsm.planning_project_stage_3')), (4, ref('industry_fsm.planning_project_stage_4'))]"/>
|
||||
<!-- BV: allow_subtask n'existe plus-->
|
||||
<!-- <field name="allow_subtasks"-->
|
||||
<!-- eval="True"/>-->
|
||||
</record>
|
||||
<function model="ir.model.data" name="write">
|
||||
<function name="search" model="ir.model.data">
|
||||
<value eval="[('name', '=', 'fsm_project'), ('module', '=', 'industry_fsm'), ('model', '=', 'project.project')]"/>
|
||||
</function>
|
||||
<value eval="{'noupdate': True}"/>
|
||||
</function>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -8,3 +8,4 @@ from . import equipment_tag
|
|||
from . import task
|
||||
from . import res_partner
|
||||
from . import fsm_visit
|
||||
from . import res_company
|
||||
8
bemade_fsm/models/res_company.py
Normal file
8
bemade_fsm/models/res_company.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from odoo import models, fields
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
split_time_from_materials_on_service_work_orders = fields.Boolean(default=False)
|
||||
create_default_fsm_visit = fields.Boolean(default=False)
|
||||
|
|
@ -115,7 +115,7 @@ class SaleOrderLine(models.Model):
|
|||
vals['parent_id'] = parent and parent.id
|
||||
vals['user_ids'] = template.assignees.ids
|
||||
vals['tag_ids'] = template.tags.ids
|
||||
vals['planned_hours'] = template.planned_hours
|
||||
vals['allocated_hours'] = template.planned_hours
|
||||
vals['sequence'] = template.sequence
|
||||
if template.equipment_ids:
|
||||
vals['equipment_ids'] = template.equipment_ids.ids
|
||||
|
|
@ -134,7 +134,7 @@ class SaleOrderLine(models.Model):
|
|||
task.message_post(body=task_msg)
|
||||
if not task.equipment_ids and self.equipment_ids:
|
||||
task.equipment_ids = self.equipment_ids.ids
|
||||
task.planned_hours = self.task_duration
|
||||
task.allocated_hours = self.task_duration
|
||||
return task
|
||||
|
||||
def _timesheet_service_generation(self):
|
||||
|
|
@ -150,7 +150,7 @@ class SaleOrderLine(models.Model):
|
|||
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})
|
||||
self.mapped('task_id').synchronize_name_fsm()
|
||||
self.task_id.filtered("is_fsm").synchronize_name_fsm()
|
||||
|
||||
def _generate_task_for_visit_line(self, project):
|
||||
self.ensure_one()
|
||||
|
|
@ -164,7 +164,7 @@ class SaleOrderLine(models.Model):
|
|||
'partner_id': self.order_id.partner_shipping_id.id,
|
||||
'visit_id': self.visit_id.id,
|
||||
'date_deadline': self.visit_id.approx_date,
|
||||
'planned_hours': self.task_duration,
|
||||
'allocated_hours': self.task_duration,
|
||||
'user_ids': False, # Force to empty or it uses the current user
|
||||
})
|
||||
return task
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from odoo import fields, models, api, Command, _
|
|||
from odoo.exceptions import ValidationError, UserError
|
||||
from odoo.osv import expression
|
||||
from collections import defaultdict, namedtuple
|
||||
from odoo.addons.project.models.project_task import CLOSED_STATES
|
||||
import re
|
||||
|
||||
|
||||
|
|
@ -82,6 +83,15 @@ class Task(models.Model):
|
|||
default=False,
|
||||
)
|
||||
|
||||
is_closed = fields.Boolean(
|
||||
compute="_compute_is_closed",
|
||||
)
|
||||
|
||||
def _compute_is_closed(self):
|
||||
for rec in self:
|
||||
rec.is_closed = rec.state in CLOSED_STATES
|
||||
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals):
|
||||
res = super().create(vals)
|
||||
|
|
@ -99,9 +109,9 @@ class Task(models.Model):
|
|||
return res
|
||||
|
||||
def write(self, vals):
|
||||
super().write(vals)
|
||||
res = super().write(vals)
|
||||
if not self: # End recursion on empty RecordSet
|
||||
return
|
||||
return res
|
||||
if 'propagate_assignment' in vals:
|
||||
# When a user sets propagate assignment, it should propagate that setting all the way down the chain
|
||||
self.child_ids.write({'propagate_assignment': vals['propagate_assignment']})
|
||||
|
|
@ -110,6 +120,7 @@ class Task(models.Model):
|
|||
# Here we use child_ids instead of _get_all_subtasks() so as to allow for setting propagate_assignment
|
||||
# to false on a child task.
|
||||
to_propagate.child_ids.write({'user_ids': vals['user_ids']})
|
||||
return res
|
||||
|
||||
@api.depends('sale_order_id')
|
||||
def _compute_relevant_order_lines(self):
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class TaskTemplate(models.Model):
|
|||
'parent_id': parent_id,
|
||||
'user_ids': self.assignees.ids,
|
||||
'tag_ids': self.tags.ids,
|
||||
'planned_hours': self.planned_hours,
|
||||
'allocated_hours': self.planned_hours,
|
||||
'sequence': self.sequence,
|
||||
'equipment_ids': [Command.set(self.equipment_ids.ids)] if self.equipment_ids else False,
|
||||
'partner_id': project.partner_id and project.partner_id.id,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
<data>
|
||||
<template id="workorder_page_materials_table">
|
||||
<t t-set="order_lines"
|
||||
t-value="doc.relevant_order_lines.filtered(lambda l:
|
||||
l.product_id.type != 'service' and not l.is_downpayment and not l.visit_id)"/>
|
||||
t-value="doc.relevant_order_lines"/>
|
||||
<!--.filtered(lambda l:
|
||||
l.product_id.type != 'service' and not l.is_downpayment and not l.visit_id)"/>-->
|
||||
<t t-if="order_lines">
|
||||
<t t-set="visit_lines" t-value="order_lines.mapped('visit_id')"/>
|
||||
<t t-set="section_lines"
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
<t t-foreach="order_lines" t-as="line">
|
||||
<t t-set="is_task" t-value="line == doc.sale_line_id"/>
|
||||
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
|
||||
<t t-if="not line.display_type">
|
||||
<t t-if="not line.display_type and not line.is_downpayment">
|
||||
<td><span t-field="line.name"/></td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.product_uom_qty"/>
|
||||
|
|
@ -284,14 +285,14 @@
|
|||
</t>
|
||||
</div>
|
||||
<div t-attf-class="{{('col-6' if report_type == 'pdf' else 'col-md-6 col-12') + ' mb-3'}}"
|
||||
t-if="doc.planned_date_begin or doc.planned_date_end">
|
||||
t-if="doc.planned_date_begin or doc.date_deadline">
|
||||
<div t-if="doc.planned_date_begin"><h6>Planned start: </h6></div>
|
||||
<div class="mb-3">
|
||||
<div t-out="doc.planned_date_begin.strftime('%Y-%m-%d %H:%M')"/>
|
||||
</div>
|
||||
<div t-if="doc.planned_date_end"><h6>Planned end: </h6></div>
|
||||
<div t-if="doc.date_deadline"><h6>Planned end: </h6></div>
|
||||
<div class="mb-3">
|
||||
<div t-out="doc.planned_date_end.strftime('%Y-%m-%d %H:%M')"/>
|
||||
<div t-out="doc.date_deadline.strftime('%Y-%m-%d %H:%M')"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -444,7 +445,12 @@
|
|||
<div class="page">
|
||||
<t t-call="bemade_fsm.workorder_page_info_block"/>
|
||||
<t t-call="bemade_fsm.workorder_page_timesheet_entries"/>
|
||||
<t t-call="bemade_fsm.workorder_page_materials_table"/>
|
||||
<t t-if="doc.company_id.split_time_from_materials_on_service_work_orders">
|
||||
<t t-call="bemade_fsm.workorder_page_materials_table"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour"/>
|
||||
</t>
|
||||
<t t-call="bemade_fsm.workorder_equipment_summary"/>
|
||||
<t t-call="bemade_fsm.workorder_page_signature_block"/>
|
||||
<t t-call="bemade_fsm.workorder_page_tasks_table"/>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,11 @@
|
|||
<odoo>
|
||||
<data>
|
||||
<record id="industry_fsm_report.task_custom_report" model="ir.actions.report">
|
||||
<field name="name">Work Order Report (PDF)</field>
|
||||
<field name="report_name">bemade_fsm.work_order</field>
|
||||
<field name="report_file">bemade_fsm.work_order</field>
|
||||
<field name="print_report_name">'%s %s' % (
|
||||
object.planned_date_begin.strftime('%Y-%m-%d') if object.planned_date_begin else time.strftime('%Y-%m-%d'),
|
||||
object.name
|
||||
)
|
||||
</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ from . import test_equipment
|
|||
from . import test_fsm_contact_setting
|
||||
from . import test_fsm_visit
|
||||
from . import test_task
|
||||
from . import test_task_report
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ from odoo import Command
|
|||
|
||||
@tagged("-at_install", "post_install")
|
||||
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
|
||||
def _generate_project_manager_user(cls, name, login):
|
||||
|
|
@ -37,7 +43,11 @@ class BemadeFSMBaseTest(TransactionCase):
|
|||
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_manager = cls.env.ref('sales_team.group_sale_manager')
|
||||
user_product_customer = cls.env.ref('customer_product_code.group_product_customer_code_user')
|
||||
# TODO: Split this out into a bemade_fsm_customer_product_code module if it's wanted
|
||||
user_product_customer = cls.env.ref(
|
||||
'customer_product_code.group_product_customer_code_user',
|
||||
raise_if_not_found=False
|
||||
)
|
||||
|
||||
group_ids = [user_group_employee.id,
|
||||
user_group_project_user.id,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,3 @@ class TestEquipment(BemadeFSMBaseTest):
|
|||
partner_company.write({'equipment_ids': [Command.set([])]})
|
||||
with self.assertRaises(MissingError):
|
||||
equipment.name
|
||||
|
||||
def test_compute_complete_name_when_name_blank(self):
|
||||
equipment = self._generate_equipment(name=False)
|
||||
complete_name = equipment.complete_name
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
|||
so.action_confirm()
|
||||
|
||||
visit_task = visit.task_id
|
||||
self.assertEqual(visit_task.planned_hours, 8.0)
|
||||
self.assertEqual(visit_task.allocated_hours, 8.0)
|
||||
|
||||
def test_adding_visit_creates_one_sale_order_line(self):
|
||||
partner = self._generate_partner()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class TestSalesOrder(BemadeFSMBaseTest):
|
|||
task = sol.task_id
|
||||
self.assertTrue(task)
|
||||
self.assertTrue(task_template.name in task.name)
|
||||
self.assertTrue(task_template.planned_hours == task.planned_hours)
|
||||
self.assertTrue(task_template.planned_hours == task.allocated_hours)
|
||||
|
||||
def test_task_template_tree_order_confirmation(self):
|
||||
partner = self._generate_partner()
|
||||
|
|
|
|||
38
bemade_fsm/tests/test_task_report.py
Normal file
38
bemade_fsm/tests/test_task_report.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
from .test_bemade_fsm_common import BemadeFSMBaseTest
|
||||
from odoo.tests import Form
|
||||
|
||||
|
||||
class TestTaskReport(BemadeFSMBaseTest):
|
||||
def test_split_time_materials_setting(self):
|
||||
settings = Form(self.env['res.config.settings'])
|
||||
settings.module_bemade_fsm_separate_time_on_work_orders = True
|
||||
settings.save()
|
||||
|
||||
so = self._generate_sale_order()
|
||||
service_product = self._generate_product()
|
||||
material_product = self._generate_product(
|
||||
name="Material Product",
|
||||
product_type='product',
|
||||
service_tracking='no',
|
||||
)
|
||||
sol = self._generate_sale_order_line(sale_order=so, product=service_product)
|
||||
sol2 = self._generate_sale_order_line(sale_order=so, product=material_product)
|
||||
so.action_confirm()
|
||||
task = sol.task_id
|
||||
|
||||
html_content = self.env['ir.actions.report']._render(
|
||||
'industry_fsm_report.worksheet_custom',
|
||||
[task.id],
|
||||
)[0].decode('utf-8').split('\n')
|
||||
|
||||
strings_to_find = [
|
||||
"<h2>Materials</h2>",
|
||||
"<span>Material Product</span>"
|
||||
]
|
||||
|
||||
for line in strings_to_find:
|
||||
line_found = False
|
||||
for html_line in html_content:
|
||||
if line in html_line:
|
||||
line_found = True
|
||||
self.assertTrue(line_found, f"{line} should be in file.")
|
||||
|
|
@ -1 +1,2 @@
|
|||
from . import new_task_from_template
|
||||
from . import res_config_settings
|
||||
|
|
|
|||
23
bemade_fsm/wizard/res_config_settings.py
Normal file
23
bemade_fsm/wizard/res_config_settings.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
module_bemade_fsm_separate_time_on_work_orders = fields.Boolean("Separate Time from Materials on Work Order")
|
||||
module_bemade_fsm_create_default_fsm_visit = fields.Boolean("Create Default Visit for FSM Sales Orders")
|
||||
|
||||
def set_values(self):
|
||||
super().set_values()
|
||||
self.env.company.split_time_from_materials_on_service_work_orders = \
|
||||
self.module_bemade_fsm_separate_time_on_work_orders
|
||||
self.env.company.create_default_fsm_visit = self.module_bemade_fsm_create_default_fsm_visit
|
||||
|
||||
def get_values(self):
|
||||
res = super().get_values()
|
||||
res.update({
|
||||
'module_bemade_fsm_separate_time_on_work_orders':
|
||||
self.env.company.split_time_from_materials_on_service_work_orders,
|
||||
'module_bemade_fsm_create_default_fsm_visit': self.env.company.create_default_fsm_visit
|
||||
})
|
||||
return res
|
||||
22
bemade_fsm/wizard/res_config_settings.xml
Normal file
22
bemade_fsm/wizard/res_config_settings.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.bemade.fsm</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="industry_fsm.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<setting name="module_industry_fsm_report" position="after">
|
||||
<setting
|
||||
name="module_bemade_fsm_separate_time_on_work_orders"
|
||||
help="Separate blocks for materials and time on work order reports.">
|
||||
<field name="module_bemade_fsm_separate_time_on_work_orders"/>
|
||||
</setting>
|
||||
<setting
|
||||
name="module_bemade_fsm_create_default_fsm_visit"
|
||||
help="Create a default FSM visit if a service SO doesn't already contain one at confirmation.">
|
||||
<field name="module_bemade_fsm_create_default_fsm_visit"/>
|
||||
</setting>
|
||||
</setting>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -7,12 +7,14 @@
|
|||
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="margin" position="replace">
|
||||
<field name="margin" invisible="True"/>
|
||||
<field name="margin_actual" class="oe_inline"/>
|
||||
</field>
|
||||
<label for="margin" position="replace">
|
||||
<label for="margin_actual" groups="base.group_user"/>
|
||||
</label>
|
||||
<field name="margin_percent" position="replace">
|
||||
<field name="margin_percent" invisible="True"/>
|
||||
<field name="margin_percent_actual" nolabel="1" class="oe_inline"
|
||||
widget="percentage" groups="base.group_user"/>
|
||||
</field>
|
||||
|
|
@ -24,6 +26,7 @@
|
|||
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order_line"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="purchase_price" position="replace">
|
||||
<field name="purchase_price" invisible="True"/>
|
||||
<field name="purchase_price_actual" groups="base.group_user"/>
|
||||
</field>
|
||||
</field>
|
||||
|
|
@ -34,12 +37,15 @@
|
|||
<field name="model">sale.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="purchase_price" position="replace">
|
||||
<field name="purchase_price" invisible="True"/>
|
||||
<field name="purchase_price_actual" optional="hide"/>
|
||||
</field>
|
||||
<field name="margin" position="replace">
|
||||
<field name="margin" invisible="True"/>
|
||||
<field name="margin_actual" optional="hide"/>
|
||||
</field>
|
||||
<field name="margin_percent" position="replace">
|
||||
<field name="margin_percent" invisible="True"/>
|
||||
<field name="margin_percent_actual" optional="hide"
|
||||
widget="percentage" groups="base.group_user"/>
|
||||
</field>
|
||||
|
|
|
|||
Loading…
Reference in a new issue