split model
This commit is contained in:
parent
ac9fb2b141
commit
839a2e2d32
11 changed files with 562 additions and 418 deletions
|
|
@ -1,7 +1,10 @@
|
||||||
from . import task_template
|
from . import task_template
|
||||||
from . import product_template
|
from . import product_template
|
||||||
|
from . import sale_order_line
|
||||||
from . import sale_order
|
from . import sale_order
|
||||||
from . import equipment
|
from . import equipment
|
||||||
|
from . import equipment_type
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,6 @@
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class EquipmentTag(models.Model):
|
|
||||||
_name = "bemade_fsm.equipment.tag"
|
|
||||||
_description = 'Field service equipment category'
|
|
||||||
|
|
||||||
name = fields.Char('Name', required=True, translate=True)
|
|
||||||
color = fields.Integer('Color Index', default=10)
|
|
||||||
|
|
||||||
_sql_constraints = [
|
|
||||||
('name_uniq', 'unique (name)', "Tag name already exists !"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EquipmentType(models.Model):
|
|
||||||
_name = 'bemade_fsm.equipment.type'
|
|
||||||
_description = 'Field service equipment type'
|
|
||||||
_order = 'id'
|
|
||||||
|
|
||||||
name = fields.Char(string='Intervention Name', required=True, translate=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Equipment(models.Model):
|
class Equipment(models.Model):
|
||||||
_name = 'bemade_fsm.equipment'
|
_name = 'bemade_fsm.equipment'
|
||||||
_rec_name = 'complete_name'
|
_rec_name = 'complete_name'
|
||||||
|
|
@ -33,26 +13,30 @@ class Equipment(models.Model):
|
||||||
|
|
||||||
complete_name = fields.Char(string="Equipment Name", compute="_compute_complete_name", store=True)
|
complete_name = fields.Char(string="Equipment Name", compute="_compute_complete_name", store=True)
|
||||||
|
|
||||||
tag_ids = fields.Many2many('bemade_fsm.equipment.tag',
|
tag_ids = fields.Many2many(
|
||||||
string='Application',
|
comodel_name='bemade_fsm.equipment.tag',
|
||||||
help="Classify and analyze your equipment categories like: Boiler, Laboratory, "
|
string='Application',
|
||||||
"Waste water, Pure water")
|
help="Classify and analyze your equipment categories like: Boiler, Laboratory, Waste water, Pure water"
|
||||||
|
)
|
||||||
|
|
||||||
description = fields.Text(string="Description",
|
description = fields.Text(string="Description", tracking=True)
|
||||||
tracking=True)
|
|
||||||
|
|
||||||
partner_location_id = fields.Many2one('res.partner',
|
partner_location_id = fields.Many2one(
|
||||||
string="Physical Address",
|
comodel_name='res.partner',
|
||||||
tracking=True,
|
string="Physical Address",
|
||||||
ondelete='cascade')
|
tracking=True,
|
||||||
|
ondelete='cascade'
|
||||||
|
)
|
||||||
|
|
||||||
location_notes = fields.Text(string="Physical Location Notes",
|
location_notes = fields.Text(string="Physical Location Notes", tracking=True)
|
||||||
tracking=True)
|
|
||||||
task_ids = fields.Many2many(comodel_name='project.task',
|
task_ids = fields.Many2many(
|
||||||
relation="bemade_fsm_task_equipment_rel",
|
comodel_name='project.task',
|
||||||
column1="equipment_id",
|
relation="bemade_fsm_task_equipment_rel",
|
||||||
column2="task_id",
|
column1="equipment_id",
|
||||||
string='Interventions')
|
column2="task_id",
|
||||||
|
string='Interventions'
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('partner_location_id')
|
@api.depends('partner_location_id')
|
||||||
def _compute_partner(self):
|
def _compute_partner(self):
|
||||||
|
|
|
||||||
13
bemade_fsm/models/equipment_tag.py
Normal file
13
bemade_fsm/models/equipment_tag.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class EquipmentTag(models.Model):
|
||||||
|
_name = "bemade_fsm.equipment.tag"
|
||||||
|
_description = 'Field service equipment category'
|
||||||
|
|
||||||
|
name = fields.Char('Name', required=True, translate=True)
|
||||||
|
color = fields.Integer('Color Index', default=10)
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
('name_uniq', 'unique (name)', "Tag name already exists !"),
|
||||||
|
]
|
||||||
9
bemade_fsm/models/equipment_type.py
Normal file
9
bemade_fsm/models/equipment_type.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class EquipmentType(models.Model):
|
||||||
|
_name = 'bemade_fsm.equipment.type'
|
||||||
|
_description = 'Field service equipment type'
|
||||||
|
_order = 'id'
|
||||||
|
|
||||||
|
name = fields.Char(string='Intervention Name', required=True, translate=True)
|
||||||
|
|
@ -5,31 +5,43 @@ 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",
|
label = fields.Text(string="Label", required=True, related='so_section_id.name', readonly=False)
|
||||||
required=True,
|
|
||||||
related='so_section_id.name',
|
|
||||||
readonly=False)
|
|
||||||
approx_date = fields.Date(string='Approximate Date')
|
approx_date = fields.Date(string='Approximate Date')
|
||||||
so_section_id = fields.Many2one(comodel_name="sale.order.line",
|
|
||||||
string="Sale Order Section",
|
so_section_id = fields.Many2one(
|
||||||
help="The section on the sale order that represents the labour and parts for "
|
comodel_name="sale.order.line",
|
||||||
"this visit",
|
string="Sale Order Section",
|
||||||
ondelete="cascade")
|
help="The section on the sale order that represents the labour and parts for this visit",
|
||||||
sale_order_id = fields.Many2one(comodel_name="sale.order",
|
ondelete="cascade"
|
||||||
string="Sales Order",
|
)
|
||||||
required=True)
|
|
||||||
is_completed = fields.Boolean(string="Completed",
|
sale_order_id = fields.Many2one(
|
||||||
related="so_section_id.is_fully_delivered")
|
comodel_name="sale.order",
|
||||||
is_invoiced = fields.Boolean(string="Invoiced",
|
string="Sales Order",
|
||||||
related="so_section_id.is_fully_delivered_and_invoiced")
|
required=True
|
||||||
summarized_equipment_ids = fields.Many2many(comodel_name="bemade_fsm.equipment",
|
)
|
||||||
string="Equipment to Service",
|
|
||||||
compute="_compute_summarized_equipment_ids")
|
is_completed = fields.Boolean(string="Completed", related="so_section_id.is_fully_delivered")
|
||||||
task_id = fields.Many2one(comodel_name="project.task",
|
|
||||||
compute="_compute_task_id",
|
is_invoiced = fields.Boolean(string="Invoiced", related="so_section_id.is_fully_delivered_and_invoiced")
|
||||||
string="Service Visit",)
|
|
||||||
task_ids = fields.One2many(comodel_name="project.task",
|
summarized_equipment_ids = fields.Many2many(
|
||||||
inverse_name="visit_id")
|
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')
|
@api.depends('task_ids')
|
||||||
def _compute_task_id(self):
|
def _compute_task_id(self):
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,13 @@ from odoo import fields, models, api
|
||||||
class ProductTemplate(models.Model):
|
class ProductTemplate(models.Model):
|
||||||
_inherit = 'product.template'
|
_inherit = 'product.template'
|
||||||
|
|
||||||
task_template_id = fields.Many2one("project.task.template", string="Task Template", ondelete='restrict')
|
task_template_id = fields.Many2one(
|
||||||
is_field_service = fields.Boolean("Plan as field service",
|
comodel_name="project.task.template",
|
||||||
help="Products planned as field service will have travel time "
|
string="Task Template",
|
||||||
"considered in planning.")
|
ondelete='restrict'
|
||||||
|
)
|
||||||
|
|
||||||
|
is_field_service = fields.Boolean(
|
||||||
|
string="Plan as field service",
|
||||||
|
help="Products planned as field service will have travel time considered in planning."
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,45 +4,60 @@ from odoo import api, fields, models, Command
|
||||||
class Partner(models.Model):
|
class Partner(models.Model):
|
||||||
_inherit = 'res.partner'
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
equipment_count = fields.Integer(compute='_compute_equipment_count',
|
equipment_count = fields.Integer(compute='_compute_equipment_count', string='Equipment Count')
|
||||||
string='Equipment Count')
|
|
||||||
|
|
||||||
owned_equipment_ids = fields.One2many(comodel_name="bemade_fsm.equipment",
|
owned_equipment_ids = fields.One2many(
|
||||||
compute="_compute_owned_equipment_ids",
|
comodel_name="bemade_fsm.equipment",
|
||||||
string="Owned Equipments")
|
compute="_compute_owned_equipment_ids",
|
||||||
|
string="Owned Equipments"
|
||||||
|
)
|
||||||
|
|
||||||
equipment_ids = fields.One2many(comodel_name='bemade_fsm.equipment',
|
equipment_ids = fields.One2many(
|
||||||
inverse_name='partner_location_id',
|
comodel_name='bemade_fsm.equipment',
|
||||||
string='Site Equipment')
|
inverse_name='partner_location_id',
|
||||||
|
string='Site Equipment'
|
||||||
|
)
|
||||||
|
|
||||||
is_site_contact = fields.Boolean(string='Is a site contact',
|
is_site_contact = fields.Boolean(
|
||||||
compute="_compute_is_site_contact",
|
string='Is a site contact',
|
||||||
search="_search_is_site_contact", )
|
compute="_compute_is_site_contact",
|
||||||
|
search="_search_is_site_contact",
|
||||||
|
)
|
||||||
|
|
||||||
site_ids = fields.Many2many(string='Work Sites',
|
site_ids = fields.Many2many(
|
||||||
comodel_name='res.partner',
|
string='Work Sites',
|
||||||
relation='res_partner_site_contact_rel',
|
comodel_name='res.partner',
|
||||||
column1='site_contact_id',
|
relation='res_partner_site_contact_rel',
|
||||||
column2='site_id',
|
column1='site_contact_id',
|
||||||
tracking=True)
|
column2='site_id',
|
||||||
|
tracking=True
|
||||||
|
)
|
||||||
|
|
||||||
site_contacts = fields.Many2many(string='Site Contacts',
|
site_contacts = fields.Many2many(
|
||||||
comodel_name='res.partner',
|
string='Site Contacts',
|
||||||
relation='res_partner_site_contact_rel',
|
comodel_name='res.partner',
|
||||||
column1='site_id',
|
relation='res_partner_site_contact_rel',
|
||||||
column2='site_contact_id',
|
column1='site_id',
|
||||||
domain=[('is_company', '=', False)],
|
column2='site_contact_id',
|
||||||
tracking=True)
|
domain=[('is_company', '=', False)],
|
||||||
|
tracking=True
|
||||||
|
)
|
||||||
|
|
||||||
work_order_contacts = fields.Many2many(string='Work Order Recipients',
|
work_order_contacts = fields.Many2many(
|
||||||
comodel_name='res.partner',
|
string='Work Order Recipients',
|
||||||
relation='res_partner_work_order_contacts_rel',
|
comodel_name='res.partner',
|
||||||
column1='res_partner_id',
|
relation='res_partner_work_order_contacts_rel',
|
||||||
column2='work_order_contact_id',
|
column1='res_partner_id',
|
||||||
domain=[('is_company', '=', False)],
|
column2='work_order_contact_id',
|
||||||
tracking=True)
|
domain=[('is_company', '=', False)],
|
||||||
|
tracking=True
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('equipment_ids', 'child_ids.company_type', 'child_ids.equipment_ids')
|
@api.depends(
|
||||||
|
'equipment_ids',
|
||||||
|
'child_ids.company_type',
|
||||||
|
'child_ids.equipment_ids'
|
||||||
|
)
|
||||||
def _compute_owned_equipment_ids(self):
|
def _compute_owned_equipment_ids(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
ids = rec.equipment_ids | rec.child_ids.filtered(
|
ids = rec.equipment_ids | rec.child_ids.filtered(
|
||||||
|
|
|
||||||
|
|
@ -7,35 +7,48 @@ import re
|
||||||
class SaleOrder(models.Model):
|
class SaleOrder(models.Model):
|
||||||
_inherit = 'sale.order'
|
_inherit = 'sale.order'
|
||||||
|
|
||||||
valid_equipment_ids = fields.One2many(comodel_name="bemade_fsm.equipment",
|
valid_equipment_ids = fields.One2many(
|
||||||
related="partner_id.owned_equipment_ids")
|
comodel_name="bemade_fsm.equipment",
|
||||||
default_equipment_ids = fields.Many2many(comodel_name="bemade_fsm.equipment",
|
related="partner_id.owned_equipment_ids"
|
||||||
string="Default Equipment to Service",
|
)
|
||||||
help="The default equipment to service for new sale order lines.",
|
|
||||||
compute="_compute_default_equipment",
|
|
||||||
inverse="_inverse_default_equipment",
|
|
||||||
store=True, )
|
|
||||||
|
|
||||||
summary_equipment_ids = fields.Many2many(comodel_name="bemade_fsm.equipment",
|
default_equipment_ids = fields.Many2many(
|
||||||
string="Equipment Being Serviced",
|
comodel_name="bemade_fsm.equipment",
|
||||||
compute="_compute_summary_equipment_ids")
|
string="Default Equipment to Service",
|
||||||
|
help="The default equipment to service for new sale order lines.",
|
||||||
|
compute="_compute_default_equipment",
|
||||||
|
inverse="_inverse_default_equipment",
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
site_contacts = fields.Many2many(comodel_name='res.partner',
|
summary_equipment_ids = fields.Many2many(
|
||||||
relation="sale_order_site_contacts_rel",
|
comodel_name="bemade_fsm.equipment",
|
||||||
compute="_compute_default_contacts",
|
string="Equipment Being Serviced",
|
||||||
inverse="_inverse_default_contacts",
|
compute="_compute_summary_equipment_ids")
|
||||||
string='Site Contacts',
|
|
||||||
store=True)
|
|
||||||
|
|
||||||
work_order_contacts = fields.Many2many(comodel_name='res.partner',
|
site_contacts = fields.Many2many(
|
||||||
relation='sale_order_work_order_contacts_rel',
|
comodel_name='res.partner',
|
||||||
compute='_compute_default_contacts',
|
relation="sale_order_site_contacts_rel",
|
||||||
inverse='_inverse_default_contacts',
|
compute="_compute_default_contacts",
|
||||||
string='Work Order Recipients',
|
inverse="_inverse_default_contacts",
|
||||||
store=True)
|
string='Site Contacts',
|
||||||
visit_ids = fields.One2many(comodel_name='bemade_fsm.visit',
|
store=True
|
||||||
inverse_name="sale_order_id",
|
)
|
||||||
readonly=False)
|
|
||||||
|
work_order_contacts = fields.Many2many(
|
||||||
|
comodel_name='res.partner',
|
||||||
|
relation='sale_order_work_order_contacts_rel',
|
||||||
|
compute='_compute_default_contacts',
|
||||||
|
inverse='_inverse_default_contacts',
|
||||||
|
string='Work Order Recipients',
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
visit_ids = fields.One2many(
|
||||||
|
comodel_name='bemade_fsm.visit',
|
||||||
|
inverse_name="sale_order_id",
|
||||||
|
readonly=False
|
||||||
|
)
|
||||||
|
|
||||||
@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):
|
||||||
|
|
@ -67,9 +80,12 @@ class SaleOrder(models.Model):
|
||||||
def _inverse_default_contacts(self):
|
def _inverse_default_contacts(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@api.depends('partner_id', 'partner_shipping_id',
|
@api.depends(
|
||||||
'partner_shipping_id.equipment_ids',
|
'partner_id',
|
||||||
'partner_id.owned_equipment_ids')
|
'partner_shipping_id',
|
||||||
|
'partner_shipping_id.equipment_ids',
|
||||||
|
'partner_id.owned_equipment_ids'
|
||||||
|
)
|
||||||
def _compute_default_equipment(self):
|
def _compute_default_equipment(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.partner_shipping_id.equipment_ids:
|
if rec.partner_shipping_id.equipment_ids:
|
||||||
|
|
@ -82,241 +98,3 @@ class SaleOrder(models.Model):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SaleOrderLine(models.Model):
|
|
||||||
_inherit = 'sale.order.line'
|
|
||||||
valid_equipment_ids = fields.One2many(comodel_name="bemade_fsm.equipment",
|
|
||||||
related="order_id.valid_equipment_ids")
|
|
||||||
visit_ids = fields.One2many(comodel_name="bemade_fsm.visit",
|
|
||||||
inverse_name="so_section_id", )
|
|
||||||
visit_id = fields.Many2one(comodel_name="bemade_fsm.visit",
|
|
||||||
compute="_compute_visit_id",
|
|
||||||
string="Visit",
|
|
||||||
ondelete='cascade',
|
|
||||||
store=True)
|
|
||||||
is_fully_delivered = fields.Boolean(string="Fully Delivered",
|
|
||||||
compute="_compute_is_fully_delivered",
|
|
||||||
help="Indicates whether a line or all the lines in a section have been"
|
|
||||||
" entirely delivered.")
|
|
||||||
is_fully_delivered_and_invoiced = fields.Boolean(string="Fully Invoiced",
|
|
||||||
compute="_compute_is_fully_invoiced",
|
|
||||||
help="Indicates whether a line or all the lines in a section have been"
|
|
||||||
" entirely delivered and invoiced.")
|
|
||||||
equipment_ids = fields.Many2many(string="Equipment to Service",
|
|
||||||
comodel_name="bemade_fsm.equipment",
|
|
||||||
relation="bemade_fsm_equipment_sale_order_line_rel",
|
|
||||||
column1="sale_order_line_id",
|
|
||||||
column2="equipment_id")
|
|
||||||
is_field_service = fields.Boolean(string="Is Field Service",
|
|
||||||
compute="_compute_is_field_service",
|
|
||||||
store=True)
|
|
||||||
|
|
||||||
task_duration = fields.Float(string="Estimated Duration",
|
|
||||||
compute="_compute_task_duration", )
|
|
||||||
|
|
||||||
@api.depends('visit_ids')
|
|
||||||
def _compute_visit_id(self):
|
|
||||||
for rec in self:
|
|
||||||
rec.visit_id = rec.visit_ids and rec.visit_ids[0]
|
|
||||||
|
|
||||||
@api.depends('product_id')
|
|
||||||
def _compute_is_field_service(self):
|
|
||||||
for rec in self:
|
|
||||||
rec.is_field_service = rec.product_id.is_field_service
|
|
||||||
|
|
||||||
@api.model_create_multi
|
|
||||||
def create(self, vals):
|
|
||||||
recs = super().create(vals)
|
|
||||||
for rec in recs:
|
|
||||||
if rec.order_id.default_equipment_ids and not rec.equipment_ids:
|
|
||||||
rec.equipment_ids = rec.order_id.default_equipment_ids
|
|
||||||
return recs
|
|
||||||
|
|
||||||
def _timesheet_create_task(self, project):
|
|
||||||
""" Generate task for the given so line, and link it.
|
|
||||||
:param project: record of project.project in which the task should be created
|
|
||||||
:return task: record of the created task
|
|
||||||
|
|
||||||
Override to add the logic needed to implement task templates and equipment linkages."""
|
|
||||||
|
|
||||||
def _create_task_from_template(project, template, parent):
|
|
||||||
""" Recursively generates the task and any subtasks from a project.task.template.
|
|
||||||
|
|
||||||
:param project: project.project record to set on the task's project_id field.
|
|
||||||
:param template: project.task.template to use to create the task.
|
|
||||||
:param parent: project.task to set as the parent to this task.
|
|
||||||
"""
|
|
||||||
values = _timesheet_create_task_prepare_values_from_template(project,
|
|
||||||
template,
|
|
||||||
parent)
|
|
||||||
task = self.env['project.task'].sudo().create(values)
|
|
||||||
subtasks = []
|
|
||||||
for t in template.subtasks:
|
|
||||||
subtask = _create_task_from_template(project, t, task)
|
|
||||||
subtasks.append(subtask)
|
|
||||||
task.write({'child_ids': [Command.set([t.id for t in subtasks])]})
|
|
||||||
# We don't want to see the sub-tasks on the SO
|
|
||||||
task.child_ids.write({'sale_order_id': None, 'sale_line_id': None, })
|
|
||||||
return task
|
|
||||||
|
|
||||||
def _timesheet_create_task_prepare_values_from_template(project, template,
|
|
||||||
parent):
|
|
||||||
""" Copies the values from a project.task.template over to the set of values used to create a project.task.
|
|
||||||
|
|
||||||
:param project: project.project record to set on the task's project_id field.
|
|
||||||
Pass the project.project model or an empty recordset to leave task project_id blank.
|
|
||||||
DO NOT pass False or None as this will cause an error in _timesheet_create_task_prepare_values(project).
|
|
||||||
:param template: project.task.template to use to create the task.
|
|
||||||
:param parent: project.task to set as the parent to this task.
|
|
||||||
"""
|
|
||||||
vals = self._timesheet_create_task_prepare_values(project)
|
|
||||||
vals['name'] = template.name
|
|
||||||
vals['description'] = template.description or '' if parent else vals['description']
|
|
||||||
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['sequence'] = template.sequence
|
|
||||||
if template.equipment_ids:
|
|
||||||
vals['equipment_ids'] = template.equipment_ids.ids
|
|
||||||
return vals
|
|
||||||
|
|
||||||
tmpl = self.product_id.task_template_id
|
|
||||||
if not tmpl:
|
|
||||||
task = super()._timesheet_create_task(project)
|
|
||||||
else:
|
|
||||||
task = _create_task_from_template(project, tmpl, None)
|
|
||||||
self.write({'task_id': task.id})
|
|
||||||
# post message on task
|
|
||||||
task_msg = _(
|
|
||||||
"This task has been created from: <a href=# data-oe-model=sale.order data-oe-id=%d>%s</a> (%s)") % (
|
|
||||||
self.order_id.id, self.order_id.name, self.product_id.name)
|
|
||||||
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
|
|
||||||
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_line_ids().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})
|
|
||||||
self.mapped('task_id').synchronize_name_fsm()
|
|
||||||
|
|
||||||
def _generate_task_for_visit_line(self, project):
|
|
||||||
self.ensure_one()
|
|
||||||
|
|
||||||
task = self.env['project.task'].create({
|
|
||||||
'name': 'Temp Name',
|
|
||||||
'description': f"Parent task for {self.order_id.name}, visit {self.name}",
|
|
||||||
'project_id': project.id,
|
|
||||||
'equipment_ids': self.get_section_line_ids().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,
|
|
||||||
'date_deadline': self.visit_id.approx_date,
|
|
||||||
'planned_hours': self.task_duration,
|
|
||||||
'user_ids': False, # Force to empty or it uses the current user
|
|
||||||
})
|
|
||||||
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):
|
|
||||||
for rec in self:
|
|
||||||
rec.is_fully_delivered = rec._iterate_items_compute_bool(
|
|
||||||
lambda l: l.qty_to_deliver == 0)
|
|
||||||
|
|
||||||
@api.depends('is_fully_delivered')
|
|
||||||
def _compute_is_fully_invoiced(self):
|
|
||||||
for rec in self:
|
|
||||||
if not rec.is_fully_delivered:
|
|
||||||
rec.is_fully_delivered_and_invoiced = False
|
|
||||||
return
|
|
||||||
rec.is_fully_delivered_and_invoiced = rec._iterate_items_compute_bool(
|
|
||||||
lambda l: l.qty_to_invoice == 0)
|
|
||||||
|
|
||||||
def get_section_line_ids(self):
|
|
||||||
self.ensure_one()
|
|
||||||
assert self.display_type == 'line_section', "Cannot get section lines for a non-section."
|
|
||||||
found = False
|
|
||||||
lines = []
|
|
||||||
for line in self.order_id.order_line.sorted(lambda l: l.sequence):
|
|
||||||
if line == self:
|
|
||||||
found = True
|
|
||||||
continue
|
|
||||||
if not found:
|
|
||||||
continue
|
|
||||||
if line.display_type == 'line_section': # Stop when we hit the next section
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
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:
|
|
||||||
return single_line_func(self)
|
|
||||||
elif self.display_type == 'line_note':
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
for line in self.order_id.order_line:
|
|
||||||
found = False
|
|
||||||
if line == self:
|
|
||||||
found = True
|
|
||||||
if not found:
|
|
||||||
continue
|
|
||||||
if found and line.display_type == 'line_section':
|
|
||||||
return True
|
|
||||||
val = single_line_func(self)
|
|
||||||
if not val:
|
|
||||||
return val
|
|
||||||
return True
|
|
||||||
|
|
||||||
@api.depends('task_duration', 'product_id', 'product_id.planning_enabled', 'state')
|
|
||||||
def _compute_planning_hours_to_plan(self):
|
|
||||||
# Override the method from sale_planning to use time estimates from the task
|
|
||||||
# template if appropriate. Also compute the value for visit lines.
|
|
||||||
planning_lines = self.filtered_domain([
|
|
||||||
('product_id.planning_enabled', '=', True),
|
|
||||||
('state', 'not in', ['draft', 'sent'])
|
|
||||||
])
|
|
||||||
for line in planning_lines:
|
|
||||||
line.planning_hours_to_plan = line.task_duration
|
|
||||||
for line in self - planning_lines:
|
|
||||||
line.planning_hours_to_plan = 0.0
|
|
||||||
|
|
||||||
@api.depends('product_id', 'visit_id')
|
|
||||||
def _compute_task_duration(self):
|
|
||||||
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)
|
|
||||||
visit_lines = self.filtered(lambda l: l.visit_id)
|
|
||||||
regular_lines = self - templated_lines - visit_lines
|
|
||||||
for line in regular_lines:
|
|
||||||
if line.product_uom == uom_hour or line.product_uom == uom_unit:
|
|
||||||
line.task_duration = line.product_uom_qty
|
|
||||||
else:
|
|
||||||
line.task_duration = float_round(
|
|
||||||
line.product_uom._compute_quantity(line.product_uom_qty, uom_hour,
|
|
||||||
raise_if_failure=False),
|
|
||||||
precision_digits=2)
|
|
||||||
for line in templated_lines:
|
|
||||||
line.task_duration = line.product_id.task_template_id.planned_hours
|
|
||||||
if line.product_uom_category_id == self.env.ref(
|
|
||||||
'uom.product_uom_unit').category_id:
|
|
||||||
line.task_duration *= line.product_uom_qty
|
|
||||||
visit_lines = self.filtered(lambda l: l.visit_id)
|
|
||||||
for line in visit_lines:
|
|
||||||
line.task_duration = sum(line.get_section_line_ids().
|
|
||||||
mapped('task_duration'))
|
|
||||||
|
|
|
||||||
267
bemade_fsm/models/sale_order_line.py
Normal file
267
bemade_fsm/models/sale_order_line.py
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
from odoo import fields, models, api, _, Command
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from odoo.tools import float_round
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class SaleOrderLine(models.Model):
|
||||||
|
_inherit = 'sale.order.line'
|
||||||
|
valid_equipment_ids = fields.One2many(
|
||||||
|
comodel_name="bemade_fsm.equipment",
|
||||||
|
related="order_id.valid_equipment_ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
visit_ids = fields.One2many(
|
||||||
|
comodel_name="bemade_fsm.visit",
|
||||||
|
inverse_name="so_section_id",
|
||||||
|
string="Visits"
|
||||||
|
)
|
||||||
|
|
||||||
|
visit_id = fields.Many2one(
|
||||||
|
comodel_name="bemade_fsm.visit",
|
||||||
|
compute="_compute_visit_id",
|
||||||
|
string="Visit",
|
||||||
|
ondelete='cascade',
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
is_fully_delivered = fields.Boolean(
|
||||||
|
string="Fully Delivered",
|
||||||
|
compute="_compute_is_fully_delivered",
|
||||||
|
help="Indicates whether a line or all the lines in a section have been entirely delivered."
|
||||||
|
)
|
||||||
|
|
||||||
|
is_fully_delivered_and_invoiced = fields.Boolean(
|
||||||
|
string="Fully Invoiced",
|
||||||
|
compute="_compute_is_fully_invoiced",
|
||||||
|
help="Indicates whether a line or all the lines in a section have been entirely delivered and invoiced."
|
||||||
|
)
|
||||||
|
|
||||||
|
equipment_ids = fields.Many2many(
|
||||||
|
string="Equipment to Service",
|
||||||
|
comodel_name="bemade_fsm.equipment",
|
||||||
|
relation="bemade_fsm_equipment_sale_order_line_rel",
|
||||||
|
column1="sale_order_line_id",
|
||||||
|
column2="equipment_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
is_field_service = fields.Boolean(
|
||||||
|
string="Is Field Service",
|
||||||
|
compute="_compute_is_field_service",
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
task_duration = fields.Float(
|
||||||
|
string="Estimated Duration",
|
||||||
|
compute="_compute_task_duration"
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('visit_ids')
|
||||||
|
def _compute_visit_id(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.visit_id = rec.visit_ids and rec.visit_ids[0]
|
||||||
|
|
||||||
|
@api.depends('product_id')
|
||||||
|
def _compute_is_field_service(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.is_field_service = rec.product_id.is_field_service
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals):
|
||||||
|
recs = super().create(vals)
|
||||||
|
for rec in recs:
|
||||||
|
if rec.order_id.default_equipment_ids and not rec.equipment_ids:
|
||||||
|
rec.equipment_ids = rec.order_id.default_equipment_ids
|
||||||
|
return recs
|
||||||
|
|
||||||
|
def _timesheet_create_task(self, project):
|
||||||
|
""" Generate task for the given so line, and link it.
|
||||||
|
:param project: record of project.project in which the task should be created
|
||||||
|
:return task: record of the created task
|
||||||
|
|
||||||
|
Override to add the logic needed to implement task templates and equipment linkages."""
|
||||||
|
|
||||||
|
def _create_task_from_template(project, template, parent):
|
||||||
|
""" Recursively generates the task and any subtasks from a project.task.template.
|
||||||
|
|
||||||
|
:param project: project.project record to set on the task's project_id field.
|
||||||
|
:param template: project.task.template to use to create the task.
|
||||||
|
:param parent: project.task to set as the parent to this task.
|
||||||
|
"""
|
||||||
|
values = _timesheet_create_task_prepare_values_from_template(project, template, parent)
|
||||||
|
task = self.env['project.task'].sudo().create(values)
|
||||||
|
subtasks = []
|
||||||
|
for t in template.subtasks:
|
||||||
|
subtask = _create_task_from_template(project, t, task)
|
||||||
|
subtasks.append(subtask)
|
||||||
|
task.write({'child_ids': [Command.set([t.id for t in subtasks])]})
|
||||||
|
# We don't want to see the sub-tasks on the SO
|
||||||
|
task.child_ids.write({'sale_order_id': None, 'sale_line_id': None, })
|
||||||
|
return task
|
||||||
|
|
||||||
|
def _timesheet_create_task_prepare_values_from_template(project, template,
|
||||||
|
parent):
|
||||||
|
""" Copies the values from a project.task.template over to the set of values used to create a project.task.
|
||||||
|
|
||||||
|
:param project: project.project record to set on the task's project_id field.
|
||||||
|
Pass the project.project model or an empty recordset to leave task project_id blank.
|
||||||
|
DO NOT pass False or None as this will cause an error in _timesheet_create_task_prepare_values(project).
|
||||||
|
:param template: project.task.template to use to create the task.
|
||||||
|
:param parent: project.task to set as the parent to this task.
|
||||||
|
"""
|
||||||
|
vals = self._timesheet_create_task_prepare_values(project)
|
||||||
|
vals['name'] = template.name
|
||||||
|
vals['description'] = template.description or '' if parent else vals['description']
|
||||||
|
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['sequence'] = template.sequence
|
||||||
|
if template.equipment_ids:
|
||||||
|
vals['equipment_ids'] = template.equipment_ids.ids
|
||||||
|
return vals
|
||||||
|
|
||||||
|
tmpl = self.product_id.task_template_id
|
||||||
|
if not tmpl:
|
||||||
|
task = super()._timesheet_create_task(project)
|
||||||
|
else:
|
||||||
|
task = _create_task_from_template(project, tmpl, None)
|
||||||
|
self.write({'task_id': task.id})
|
||||||
|
# post message on task
|
||||||
|
task_msg = _(
|
||||||
|
"This task has been created from: <a href=# data-oe-model=sale.order data-oe-id=%d>%s</a> (%s)") % (
|
||||||
|
self.order_id.id, self.order_id.name, self.product_id.name)
|
||||||
|
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
|
||||||
|
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_line_ids().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})
|
||||||
|
self.mapped('task_id').synchronize_name_fsm()
|
||||||
|
|
||||||
|
def _generate_task_for_visit_line(self, project):
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
task = self.env['project.task'].create({
|
||||||
|
'name': 'Temp Name',
|
||||||
|
'description': f"Parent task for {self.order_id.name}, visit {self.name}",
|
||||||
|
'project_id': project.id,
|
||||||
|
'equipment_ids': self.get_section_line_ids().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,
|
||||||
|
'date_deadline': self.visit_id.approx_date,
|
||||||
|
'planned_hours': self.task_duration,
|
||||||
|
'user_ids': False, # Force to empty or it uses the current user
|
||||||
|
})
|
||||||
|
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):
|
||||||
|
for rec in self:
|
||||||
|
rec.is_fully_delivered = rec._iterate_items_compute_bool(
|
||||||
|
lambda l: l.qty_to_deliver == 0)
|
||||||
|
|
||||||
|
@api.depends('is_fully_delivered')
|
||||||
|
def _compute_is_fully_invoiced(self):
|
||||||
|
for rec in self:
|
||||||
|
if not rec.is_fully_delivered:
|
||||||
|
rec.is_fully_delivered_and_invoiced = False
|
||||||
|
return
|
||||||
|
rec.is_fully_delivered_and_invoiced = rec._iterate_items_compute_bool(
|
||||||
|
lambda l: l.qty_to_invoice == 0)
|
||||||
|
|
||||||
|
def get_section_line_ids(self):
|
||||||
|
self.ensure_one()
|
||||||
|
assert self.display_type == 'line_section', "Cannot get section lines for a non-section."
|
||||||
|
found = False
|
||||||
|
lines = []
|
||||||
|
for line in self.order_id.order_line.sorted(lambda l: l.sequence):
|
||||||
|
if line == self:
|
||||||
|
found = True
|
||||||
|
continue
|
||||||
|
if not found:
|
||||||
|
continue
|
||||||
|
if line.display_type == 'line_section': # Stop when we hit the next section
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
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:
|
||||||
|
return single_line_func(self)
|
||||||
|
elif self.display_type == 'line_note':
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
for line in self.order_id.order_line:
|
||||||
|
found = False
|
||||||
|
if line == self:
|
||||||
|
found = True
|
||||||
|
if not found:
|
||||||
|
continue
|
||||||
|
if found and line.display_type == 'line_section':
|
||||||
|
return True
|
||||||
|
val = single_line_func(self)
|
||||||
|
if not val:
|
||||||
|
return val
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api.depends('task_duration', 'product_id', 'product_id.planning_enabled', 'state')
|
||||||
|
def _compute_planning_hours_to_plan(self):
|
||||||
|
# Override the method from sale_planning to use time estimates from the task
|
||||||
|
# template if appropriate. Also compute the value for visit lines.
|
||||||
|
planning_lines = self.filtered_domain([
|
||||||
|
('product_id.planning_enabled', '=', True),
|
||||||
|
('state', 'not in', ['draft', 'sent'])
|
||||||
|
])
|
||||||
|
for line in planning_lines:
|
||||||
|
line.planning_hours_to_plan = line.task_duration
|
||||||
|
for line in self - planning_lines:
|
||||||
|
line.planning_hours_to_plan = 0.0
|
||||||
|
|
||||||
|
@api.depends('product_id', 'visit_id')
|
||||||
|
def _compute_task_duration(self):
|
||||||
|
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)
|
||||||
|
visit_lines = self.filtered(lambda l: l.visit_id)
|
||||||
|
regular_lines = self - templated_lines - visit_lines
|
||||||
|
for line in regular_lines:
|
||||||
|
if line.product_uom == uom_hour or line.product_uom == uom_unit:
|
||||||
|
line.task_duration = line.product_uom_qty
|
||||||
|
else:
|
||||||
|
line.task_duration = float_round(
|
||||||
|
line.product_uom._compute_quantity(line.product_uom_qty, uom_hour,
|
||||||
|
raise_if_failure=False),
|
||||||
|
precision_digits=2)
|
||||||
|
for line in templated_lines:
|
||||||
|
line.task_duration = line.product_id.task_template_id.planned_hours
|
||||||
|
if line.product_uom_category_id == self.env.ref(
|
||||||
|
'uom.product_uom_unit').category_id:
|
||||||
|
line.task_duration *= line.product_uom_qty
|
||||||
|
visit_lines = self.filtered(lambda l: l.visit_id)
|
||||||
|
for line in visit_lines:
|
||||||
|
line.task_duration = sum(line.get_section_line_ids().
|
||||||
|
mapped('task_duration'))
|
||||||
|
|
@ -8,51 +8,71 @@ import re
|
||||||
class Task(models.Model):
|
class Task(models.Model):
|
||||||
_inherit = "project.task"
|
_inherit = "project.task"
|
||||||
|
|
||||||
equipment_ids = fields.Many2many(comodel_name="bemade_fsm.equipment",
|
equipment_ids = fields.Many2many(
|
||||||
relation="bemade_fsm_task_equipment_rel",
|
comodel_name="bemade_fsm.equipment",
|
||||||
column1="task_id",
|
relation="bemade_fsm_task_equipment_rel",
|
||||||
column2="equipment_id",
|
column1="task_id",
|
||||||
string="Equipment to Service",
|
column2="equipment_id",
|
||||||
tracking=True, )
|
string="Equipment to Service",
|
||||||
|
tracking=True,
|
||||||
|
)
|
||||||
|
|
||||||
work_order_contacts = fields.Many2many(comodel_name="res.partner",
|
work_order_contacts = fields.Many2many(
|
||||||
relation="task_work_order_contact_rel",
|
comodel_name="res.partner",
|
||||||
column1="task_id",
|
relation="task_work_order_contact_rel",
|
||||||
column2="res_partner_id",
|
column1="task_id",
|
||||||
compute="_compute_contacts",
|
column2="res_partner_id",
|
||||||
inverse="_inverse_contacts",
|
compute="_compute_contacts",
|
||||||
store=True)
|
inverse="_inverse_contacts",
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
site_contacts = fields.Many2many(comodel_name="res.partner",
|
site_contacts = fields.Many2many(
|
||||||
relation="task_site_contact_rel",
|
comodel_name="res.partner",
|
||||||
column1="task_id",
|
relation="task_site_contact_rel",
|
||||||
column2="res_partner_id",
|
column1="task_id",
|
||||||
compute="_compute_contacts",
|
column2="res_partner_id",
|
||||||
inverse="_inverse_contacts",
|
compute="_compute_contacts",
|
||||||
store=True)
|
inverse="_inverse_contacts",
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
planned_date_begin = fields.Datetime("Start Date", tracking=True,
|
planned_date_begin = fields.Datetime(
|
||||||
task_dependency_tracking=True,
|
string="Start Date",
|
||||||
compute="_compute_planned_dates",
|
tracking=True,
|
||||||
inverse="_inverse_planned_dates",
|
task_dependency_tracking=True,
|
||||||
store=True)
|
compute="_compute_planned_dates",
|
||||||
planned_date_end = fields.Datetime("End Date", tracking=True,
|
inverse="_inverse_planned_dates",
|
||||||
task_dependency_tracking=True,
|
store=True
|
||||||
compute="_compute_planned_dates",
|
)
|
||||||
inverse="_inverse_planned_dates",
|
|
||||||
store=True)
|
planned_date_end = fields.Datetime(
|
||||||
|
string="End Date",
|
||||||
|
tracking=True,
|
||||||
|
task_dependency_tracking=True,
|
||||||
|
compute="_compute_planned_dates",
|
||||||
|
inverse="_inverse_planned_dates",
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
# Override related field to make it return false if this is an FSM subtask
|
# Override related field to make it return false if this is an FSM subtask
|
||||||
allow_billable = fields.Boolean(string="Can be billed",
|
allow_billable = fields.Boolean(
|
||||||
related=False,
|
string="Can be billed",
|
||||||
compute="_compute_allow_billable",
|
related=False,
|
||||||
store=True)
|
compute="_compute_allow_billable",
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
visit_id = fields.Many2one(comodel_name='bemade_fsm.visit')
|
visit_id = fields.Many2one(
|
||||||
|
comodel_name='bemade_fsm.visit',
|
||||||
|
string="Visit",
|
||||||
|
)
|
||||||
|
|
||||||
relevant_order_lines = fields.Many2many(comodel_name='sale.order.line',
|
relevant_order_lines = fields.Many2many(
|
||||||
store=False,
|
comodel_name='sale.order.line',
|
||||||
compute='_compute_relevant_order_lines', )
|
store=False,
|
||||||
|
compute='_compute_relevant_order_lines',
|
||||||
|
)
|
||||||
|
|
||||||
work_order_number = fields.Char(readonly=True)
|
work_order_number = fields.Char(readonly=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,25 +10,62 @@ class TaskTemplate(models.Model):
|
||||||
return self.env.company
|
return self.env.company
|
||||||
|
|
||||||
name = fields.Char(string="Task Title", required=True)
|
name = fields.Char(string="Task Title", required=True)
|
||||||
|
|
||||||
description = fields.Html(string="Description")
|
description = fields.Html(string="Description")
|
||||||
assignees = fields.Many2many("res.users", string="Default Assignees",
|
|
||||||
help="Employees assigned to tasks created from this template.")
|
assignees = fields.Many2many(
|
||||||
customer = fields.Many2one("res.partner", string="Default Customer",
|
comodel_name="res.users",
|
||||||
help="Default customer for tasks created from this template.")
|
string="Default Assignees",
|
||||||
project = fields.Many2one("project.project", string="Default Project",
|
help="Employees assigned to tasks created from this template."
|
||||||
help="Default project for tasks created from this template.")
|
)
|
||||||
tags = fields.Many2many("project.tags", string="Default Tags",
|
|
||||||
help="Default tags for tasks created from this template.")
|
customer = fields.Many2one(
|
||||||
parent = fields.Many2one("project.task.template", string="Parent Task Template", ondelete='cascade')
|
comodel_name="res.partner",
|
||||||
subtasks = fields.One2many("project.task.template", inverse_name="parent", string="Subtask Templates")
|
string="Default Customer",
|
||||||
|
help="Default customer for tasks created from this template."
|
||||||
|
)
|
||||||
|
|
||||||
|
project = fields.Many2one(
|
||||||
|
comodel_name="project.project",
|
||||||
|
string="Default Project",
|
||||||
|
help="Default project for tasks created from this template."
|
||||||
|
)
|
||||||
|
|
||||||
|
tags = fields.Many2many(
|
||||||
|
comodel_name="project.tags",
|
||||||
|
string="Default Tags",
|
||||||
|
help="Default tags for tasks created from this template."
|
||||||
|
)
|
||||||
|
|
||||||
|
parent = fields.Many2one(
|
||||||
|
comodel_name="project.task.template",
|
||||||
|
string="Parent Task Template",
|
||||||
|
ondelete='cascade'
|
||||||
|
)
|
||||||
|
|
||||||
|
subtasks = fields.One2many(
|
||||||
|
comodel_name="project.task.template",
|
||||||
|
inverse_name="parent",
|
||||||
|
string="Subtask Templates"
|
||||||
|
)
|
||||||
sequence = fields.Integer(string="Sequence")
|
sequence = fields.Integer(string="Sequence")
|
||||||
company_id = fields.Many2one("res.company", string="Company", index=1, default=_current_company)
|
|
||||||
|
company_id = fields.Many2one(
|
||||||
|
comodel_name="res.company",
|
||||||
|
string="Company",
|
||||||
|
index=1,
|
||||||
|
default=_current_company
|
||||||
|
)
|
||||||
|
|
||||||
planned_hours = fields.Float("Initially Planned Hours")
|
planned_hours = fields.Float("Initially Planned Hours")
|
||||||
equipment_ids = fields.Many2many(comodel_name="bemade_fsm.equipment",
|
|
||||||
relation="bemade_fsm_task_template_equipment_rel",
|
equipment_ids = fields.Many2many(
|
||||||
column1="task_template_id",
|
comodel_name="bemade_fsm.equipment",
|
||||||
column2="equipment_id",
|
relation="bemade_fsm_task_template_equipment_rel",
|
||||||
string="Equipment to Service",)
|
column1="task_template_id",
|
||||||
|
column2="equipment_id",
|
||||||
|
string="Equipment to Service",
|
||||||
|
)
|
||||||
|
|
||||||
def action_open_task(self):
|
def action_open_task(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue