516 lines
24 KiB
Python
516 lines
24 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from ast import literal_eval
|
|
from markupsafe import Markup
|
|
import pytz
|
|
|
|
from odoo import fields, models, api, _
|
|
from odoo.exceptions import UserError
|
|
from odoo.fields import Command, Domain
|
|
from odoo.tools import get_lang
|
|
from odoo.tools.intervals import Intervals
|
|
from odoo.tools.date_utils import sum_intervals
|
|
from odoo.addons.timer.utils.timer_utils import round_time_spent
|
|
|
|
|
|
class ProjectTask(models.Model):
|
|
_inherit = "project.task"
|
|
|
|
@api.model
|
|
def default_get(self, fields):
|
|
context = dict(self.env.context)
|
|
is_fsm_mode = self.env.context.get('fsm_mode')
|
|
fsm_project = False
|
|
if is_fsm_mode and 'project_id' in fields and not self.env.context.get('default_parent_id'):
|
|
company_id = self.env.context.get('default_company_id') or self.env.company.id
|
|
fsm_project = self.env['project.project'].search([('is_fsm', '=', True), ('company_id', '=', company_id)], order='sequence, name, id', limit=1)
|
|
if fsm_project:
|
|
context['default_project_id'] = self.env.context.get('default_project_id', fsm_project.id)
|
|
result = super(ProjectTask, self.with_context(context)).default_get(fields)
|
|
if fsm_project:
|
|
result.update({
|
|
'company_id': company_id,
|
|
'stage_id': self.stage_find(fsm_project.id, [('fold', '=', False)])
|
|
})
|
|
return result
|
|
|
|
is_fsm = fields.Boolean(related='project_id.is_fsm')
|
|
fsm_done = fields.Boolean("Task Done", compute='_compute_fsm_done', readonly=False, store=True, copy=False)
|
|
# Use to count conditions between : time, worksheet and materials
|
|
# If 2 over 3 are enabled for the project, the required count = 2
|
|
# If 1 over 3 is met (enabled + encoded), the satisfied count = 2
|
|
display_enabled_conditions_count = fields.Integer(compute='_compute_display_conditions_count', export_string_translation=False)
|
|
display_satisfied_conditions_count = fields.Integer(compute='_compute_display_conditions_count', export_string_translation=False)
|
|
display_mark_as_done_primary = fields.Boolean(compute='_compute_mark_as_done_buttons', export_string_translation=False)
|
|
display_mark_as_done_secondary = fields.Boolean(compute='_compute_mark_as_done_buttons', export_string_translation=False)
|
|
partner_city = fields.Char(related='partner_id.city', readonly=False)
|
|
partner_zip = fields.Char(string='ZIP', related='partner_id.zip')
|
|
partner_street = fields.Char(related='partner_id.street')
|
|
partner_street2 = fields.Char(related='partner_id.street2')
|
|
partner_country_id = fields.Many2one('res.country', related='partner_id.country_id')
|
|
partner_state_id = fields.Many2one('res.country.state', string='Customer State', related='partner_id.state_id')
|
|
is_task_phone_update = fields.Boolean(compute='_compute_is_task_phone_update', export_string_translation=False)
|
|
|
|
# FSM Report fields
|
|
display_sign_report_primary = fields.Boolean(compute='_compute_display_sign_report_buttons', export_string_translation=False)
|
|
display_sign_report_secondary = fields.Boolean(compute='_compute_display_sign_report_buttons', export_string_translation=False)
|
|
display_send_report_primary = fields.Boolean(compute='_compute_display_send_report_buttons', export_string_translation=False)
|
|
display_send_report_secondary = fields.Boolean(compute='_compute_display_send_report_buttons', export_string_translation=False)
|
|
fsm_is_sent = fields.Boolean(readonly=True, copy=False, export_string_translation=False)
|
|
show_customer_preview = fields.Boolean(compute='_compute_show_customer_preview', export_string_translation=False)
|
|
worksheet_signature = fields.Binary('Signature', copy=False, attachment=True)
|
|
worksheet_signed_by = fields.Char('Signed By', copy=False)
|
|
allow_geolocation = fields.Boolean(related="project_id.allow_geolocation")
|
|
|
|
@api.depends('planned_date_begin', 'date_deadline', 'user_ids')
|
|
def _compute_planning_overlap(self):
|
|
"""Computes overlap warnings for fsm tasks.
|
|
|
|
Unlike other tasks, fsm tasks overlap in two scenarios:
|
|
1. When the combined allocated hours of an fsm task and a normal task exceed the user's workable hours.
|
|
2. When two fsm tasks have overlapping planned dates.
|
|
|
|
Example:
|
|
- ProjectTask A (normal) conflicts with ProjectTask B (fsm) if their combined hours > user's workable hours. Both have 1 conflict.
|
|
- Introduce ProjectTask C (fsm) with no allocated hours (no conflict with ProjectTask A) but same time period as ProjectTask B.
|
|
Result: ProjectTask A has 1 conflict, ProjectTask B has 2 conflicts, ProjectTask C has 1 conflict.
|
|
"""
|
|
fsm_tasks = self.filtered("is_fsm")
|
|
overlap_mapping = super()._compute_planning_overlap()
|
|
for row in self._fetch_planning_overlap([('is_fsm', '=', True)]):
|
|
task_id = row["id"]
|
|
if task_id not in overlap_mapping:
|
|
overlap_mapping[task_id] = {row["user_id"]: {}}
|
|
if row["user_id"] not in overlap_mapping[task_id]:
|
|
overlap_mapping[task_id][row["user_id"]] = {}
|
|
overlap_data = overlap_mapping[task_id][row["user_id"]]
|
|
overlap_data['partner_name'] = row['partner_name']
|
|
existing_task_ids = overlap_data.get('overlapping_tasks_ids', [])
|
|
overlap_data['overlapping_tasks_ids'] = list(set(existing_task_ids) | set(row['task_ids']))
|
|
existing_min_date = overlap_data.get('min_planned_date_begin', row['min'])
|
|
overlap_data['min_planned_date_begin'] = min(existing_min_date, row['min'])
|
|
existing_max_date = overlap_data.get('max_date_deadline', row['max'])
|
|
overlap_data['max_date_deadline'] = max(existing_max_date, row['max'])
|
|
if not overlap_mapping:
|
|
fsm_tasks.planning_overlap = False
|
|
return
|
|
for task in fsm_tasks:
|
|
overlap_messages = []
|
|
for task_mapping in overlap_mapping.get(task.id, {}).values():
|
|
message = _('%(partner)s has %(number)s tasks at the same time.', partner=task_mapping['partner_name'], number=len(task_mapping['overlapping_tasks_ids']))
|
|
overlap_messages.append(message)
|
|
task.planning_overlap = ' '.join(overlap_messages) or False
|
|
|
|
@property
|
|
def TASK_PORTAL_READABLE_FIELDS(self):
|
|
return super().TASK_PORTAL_READABLE_FIELDS | {'is_fsm',
|
|
'planned_date_begin',
|
|
'fsm_done',
|
|
'partner_phone',
|
|
'partner_city',}
|
|
|
|
@api.depends(
|
|
'fsm_done', 'is_fsm', 'timer_start',
|
|
'display_enabled_conditions_count', 'display_satisfied_conditions_count')
|
|
def _compute_mark_as_done_buttons(self):
|
|
for task in self:
|
|
primary, secondary = True, True
|
|
if task.fsm_done or not task.is_fsm or task.sudo().timer_start:
|
|
primary, secondary = False, False
|
|
else:
|
|
if task.display_enabled_conditions_count == task.display_satisfied_conditions_count:
|
|
secondary = False
|
|
else:
|
|
primary = False
|
|
task.update({
|
|
'display_mark_as_done_primary': primary,
|
|
'display_mark_as_done_secondary': secondary,
|
|
})
|
|
|
|
@api.depends('partner_phone', 'partner_id.phone')
|
|
def _compute_is_task_phone_update(self):
|
|
for task in self:
|
|
task.is_task_phone_update = task.partner_phone != task.partner_id.phone
|
|
|
|
@api.depends('project_id.allow_timesheets', 'total_hours_spent')
|
|
def _compute_display_conditions_count(self):
|
|
for task in self:
|
|
enabled = 1 if task.project_id.allow_timesheets else 0
|
|
satisfied = 1 if enabled and task.total_hours_spent else 0
|
|
task.update({
|
|
'display_enabled_conditions_count': enabled,
|
|
'display_satisfied_conditions_count': satisfied
|
|
})
|
|
|
|
@api.depends('fsm_done')
|
|
def _compute_display_timesheet_timer(self):
|
|
fsm_done_tasks = self.filtered('fsm_done')
|
|
fsm_done_tasks.display_timesheet_timer = False
|
|
super(ProjectTask, self - fsm_done_tasks)._compute_display_timesheet_timer()
|
|
|
|
@api.onchange('date_deadline', 'planned_date_begin')
|
|
def _onchange_planned_dates(self):
|
|
if not self.is_fsm:
|
|
return super()._onchange_planned_dates()
|
|
|
|
def write(self, vals):
|
|
self_fsm = self.filtered('is_fsm')
|
|
basic_projects = self - self_fsm
|
|
if basic_projects:
|
|
res = super(ProjectTask, basic_projects).write(vals.copy())
|
|
if not self_fsm:
|
|
return res
|
|
|
|
is_start_date_set = bool(vals.get('planned_date_begin', False))
|
|
is_end_date_set = bool(vals.get("date_deadline", False))
|
|
both_dates_changed = 'planned_date_begin' in vals and 'date_deadline' in vals
|
|
self_fsm = self_fsm.with_context(fsm_mode=True)
|
|
|
|
if self_fsm and (
|
|
(both_dates_changed and is_start_date_set != is_end_date_set) or (not both_dates_changed and (
|
|
('planned_date_begin' in vals and not all(bool(t.date_deadline) == is_start_date_set for t in self)) or \
|
|
('date_deadline' in vals and not all(bool(t.planned_date_begin) == is_end_date_set for t in self))
|
|
))
|
|
):
|
|
vals.update({"date_deadline": False, "planned_date_begin": False})
|
|
|
|
return super(ProjectTask, self_fsm).write(vals)
|
|
|
|
@api.model
|
|
def _group_expand_project_ids(self, projects, domain):
|
|
res = super()._group_expand_project_ids(projects, domain)
|
|
if self.env.context.get('fsm_mode'):
|
|
search_on_comodel = self._search_on_comodel(domain, "project_id", "project.project", [('is_fsm', '=', True)])
|
|
res &= search_on_comodel
|
|
return res
|
|
|
|
def _group_expand_user_ids_domain(self, domain_expand):
|
|
if self.env.context.get('fsm_mode'):
|
|
new_domain_expand = Domain.OR([[
|
|
('is_closed', '=', False),
|
|
('planned_date_begin', '=', False),
|
|
('date_deadline', '=', False),
|
|
], domain_expand])
|
|
return new_domain_expand & Domain('is_fsm', '=', True)
|
|
else:
|
|
return super()._group_expand_user_ids_domain(domain_expand)
|
|
|
|
def _compute_fsm_done(self):
|
|
closed_tasks = self.filtered(lambda t: t.is_closed)
|
|
closed_tasks.fsm_done = True
|
|
|
|
def action_timer_start(self):
|
|
if not self.user_timer_id.timer_start and self.display_timesheet_timer:
|
|
super().action_timer_start()
|
|
if not self.is_fsm:
|
|
return
|
|
|
|
if self.allow_geolocation and (geolocation := self.env.context.get("geolocation")):
|
|
success = geolocation.get("success")
|
|
latitude = 0
|
|
longitude = 0
|
|
localisation_start = False
|
|
|
|
if success:
|
|
latitude = geolocation["latitude"]
|
|
longitude = geolocation["longitude"]
|
|
localisation_start = self.env["base.geocoder"]._get_localisation(latitude=latitude, longitude=longitude)
|
|
|
|
geolocation_message = _("GPS Coordinates: %(localisation_start)s (%(latitude)s, %(longitude)s)",
|
|
localisation_start=localisation_start,
|
|
latitude=latitude,
|
|
longitude=longitude,
|
|
)
|
|
else:
|
|
geolocation_message = geolocation.get("message", _("Location error"))
|
|
else:
|
|
geolocation_message = False
|
|
|
|
time = fields.Datetime.context_timestamp(self, self.timer_start)
|
|
body = _(
|
|
'Timer started at: %(date)s %(time)s',
|
|
date=time.strftime(get_lang(self.env).date_format),
|
|
time=time.strftime(get_lang(self.env).time_format),
|
|
)
|
|
if geolocation_message:
|
|
body += Markup("<br/>") + geolocation_message
|
|
if latitude and latitude:
|
|
body += Markup(" <a href='https://maps.google.com?q={latitude},{longitude}' target='_blank'>{label}</a>").format(
|
|
latitude=latitude,
|
|
longitude=longitude,
|
|
label=_("View on Map")
|
|
)
|
|
self.message_post(body=body)
|
|
|
|
def action_view_timesheets(self):
|
|
kanban_view = self.env.ref('hr_timesheet.view_kanban_account_analytic_line')
|
|
form_view = self.env.ref('industry_fsm.timesheet_view_form')
|
|
list_view = self.env.ref('industry_fsm.timesheet_view_tree_user_inherit')
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _('Time'),
|
|
'res_model': 'account.analytic.line',
|
|
'view_mode': 'list,form,kanban',
|
|
'views': [(list_view.id, 'list'), (kanban_view.id, 'kanban'), (form_view.id, 'form')],
|
|
'domain': [('task_id', '=', self.id), ('project_id', '!=', False)],
|
|
'context': {
|
|
'fsm_mode': True,
|
|
'default_project_id': self.project_id.id,
|
|
'default_task_id': self.id,
|
|
}
|
|
}
|
|
|
|
def _get_action_fsm_task_mobile_view(self):
|
|
action = self.env['ir.actions.act_window']._for_xml_id('industry_fsm.project_task_action_fsm')
|
|
mobile_form_view = self.env.ref('industry_fsm.project_task_view_mobile_form', raise_if_not_found=False)
|
|
if mobile_form_view:
|
|
action['views'] = [(mobile_form_view.id, 'form')]
|
|
context = action.get('context', {})
|
|
context = context.replace('uid', str(self.env.uid))
|
|
context = dict(literal_eval(context), active_test=True)
|
|
fsm_project_count = self.env['project.project'].search_count([('is_fsm', '=', True)], limit=2)
|
|
context.update(
|
|
industry_fsm_one_project=fsm_project_count == 1,
|
|
industry_fsm_hide_user_ids=bool(self.user_ids) and self.env.user not in self.user_ids,
|
|
)
|
|
action['context'] = context
|
|
action['view_mode'] = 'form'
|
|
action['res_id'] = self.id
|
|
return action
|
|
|
|
def action_fsm_validate(self, stop_running_timers=False):
|
|
""" Moves Task to done state.
|
|
If allow billable on task, timesheet product set on project and user has privileges :
|
|
Create SO confirmed with time and material.
|
|
"""
|
|
Timer = self.env['timer.timer']
|
|
running_timers = Timer.search([('parent_res_model', '=', 'project.task'), ('parent_res_id', 'in', self.ids)])
|
|
if running_timers:
|
|
if stop_running_timers:
|
|
self._stop_all_timers_and_update_timesheets(running_timers)
|
|
else:
|
|
wizard = self.env['project.task.stop.timers.wizard'].create({
|
|
'line_ids': [Command.create({'task_id': task.id}) for task in self],
|
|
})
|
|
return {
|
|
'name': _('Do you want to stop the running timers?'),
|
|
'type': 'ir.actions.act_window',
|
|
'view_mode': 'form',
|
|
'view_id': self.env.ref('industry_fsm.view_task_stop_timer_wizard_form').id,
|
|
'target': 'new',
|
|
'res_model': 'project.task.stop.timers.wizard',
|
|
'res_id': wizard.id,
|
|
}
|
|
|
|
self.write({'fsm_done': True, 'state': '1_done'})
|
|
|
|
return True
|
|
|
|
@api.model
|
|
def _stop_all_timers_and_update_timesheets(self, running_timers):
|
|
ConfigParameter = self.env['ir.config_parameter'].sudo()
|
|
Timesheet = self.env['account.analytic.line']
|
|
|
|
if not running_timers:
|
|
return Timesheet
|
|
if any(
|
|
timer.res_model != 'account.analytic.line'
|
|
or (timer.parent_res_model and timer.parent_res_model != 'project.task')
|
|
for timer in running_timers
|
|
):
|
|
raise UserError(_('All running timers should be linked to a timesheet and a task.'))
|
|
|
|
result = Timesheet
|
|
minimum_duration = int(ConfigParameter.get_param('timesheet_grid.timesheet_min_duration', 0))
|
|
rounding = int(ConfigParameter.get_param('timesheet_grid.timesheet_rounding', 0))
|
|
if running_timers:
|
|
timesheets = self.env['account.analytic.line'].sudo().search([('id', 'in', running_timers.mapped('res_id'))])
|
|
timesheets_dict = {timesheet.id: timesheet for timesheet in timesheets}
|
|
for timer in running_timers:
|
|
timesheet = timesheets_dict[timer.res_id]
|
|
minutes_spent = timer._get_minutes_spent()
|
|
timesheet.write({'unit_amount': round_time_spent(minutes_spent, minimum_duration, rounding) / 60})
|
|
result += timesheet
|
|
running_timers.sudo().unlink()
|
|
|
|
return result
|
|
|
|
def action_fsm_navigate(self):
|
|
if not self.partner_id.city or not self.partner_id.country_id:
|
|
return {
|
|
'name': _('Customer'),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'res.partner',
|
|
'res_id': self.partner_id.id,
|
|
'view_mode': 'form',
|
|
'view_id': self.env.ref('industry_fsm.view_partner_address_form_industry_fsm').id,
|
|
'target': 'new',
|
|
}
|
|
return self.partner_id.action_partner_navigate()
|
|
|
|
def web_read(self, specification: dict[str, dict]) -> list[dict]:
|
|
if len(self) == 1 and 'partner_id' in specification and 'show_address_if_fsm' in specification['partner_id'].get('context', {}):
|
|
specification['partner_id']['context']['show_address'] = self.is_fsm
|
|
return super().web_read(specification)
|
|
|
|
def _server_action_project_task_fsm(self, xml_id_multiple_fsm_projects, xml_id_one_fsm_project, default_user_ids=False):
|
|
project_ids = self.env['project.project'].search([('is_fsm', '=', True)], limit=2)
|
|
if len(project_ids) <= 1:
|
|
action_id = xml_id_one_fsm_project
|
|
project_id = project_ids.id
|
|
else:
|
|
action_id = xml_id_multiple_fsm_projects
|
|
project_id = False
|
|
action = self.env['ir.actions.act_window']._for_xml_id(action_id)
|
|
context = literal_eval(action.get('context', '{}'))
|
|
allow_timesheets = any(project.allow_timesheets for project in project_ids)
|
|
if project_id:
|
|
context['default_project_id'] = project_id
|
|
return {
|
|
**action,
|
|
'context': {
|
|
**context,
|
|
'default_user_ids': default_user_ids,
|
|
'allow_timesheets': allow_timesheets,
|
|
},
|
|
'domain': Domain.AND([
|
|
literal_eval(action.get('domain') or '[]'),
|
|
[('has_template_ancestor', '=', False)]
|
|
]),
|
|
}
|
|
|
|
# ---------------------------------------------------------
|
|
# FSM Report
|
|
# ---------------------------------------------------------
|
|
|
|
def _compute_display_send_report_buttons(self):
|
|
self.display_send_report_primary = False
|
|
self.display_send_report_secondary = False
|
|
|
|
def _compute_display_sign_report_buttons(self):
|
|
self.display_sign_report_primary = False
|
|
self.display_sign_report_secondary = False
|
|
|
|
def _compute_show_customer_preview(self):
|
|
for task in self:
|
|
task.show_customer_preview = task.fsm_is_sent or task.worksheet_signature
|
|
|
|
def _has_to_be_signed(self):
|
|
self.ensure_one()
|
|
return self._is_fsm_report_available() and not self.worksheet_signature
|
|
|
|
def _is_fsm_report_available(self):
|
|
self.ensure_one()
|
|
return self.timesheet_ids
|
|
|
|
def _get_send_report_action(self):
|
|
template_id = self.env.ref('industry_fsm.mail_template_data_task_report').id
|
|
self.message_subscribe(partner_ids=self.partner_id.ids)
|
|
return {
|
|
'name': _("Send Field Service Report"),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'mail.compose.message',
|
|
'views': [(False, 'form')],
|
|
'target': 'new',
|
|
'context': {
|
|
'default_composition_mode': 'mass_mail' if len(self.ids) > 1 else 'comment',
|
|
'default_model': 'project.task',
|
|
'default_res_ids': self.ids,
|
|
'default_template_id': template_id,
|
|
'fsm_mark_as_sent': True,
|
|
},
|
|
}
|
|
|
|
def action_send_report(self):
|
|
tasks_with_report = self.filtered(lambda task: task._is_fsm_report_available())
|
|
if not tasks_with_report:
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'message': _("There are no reports to send."),
|
|
'sticky': False,
|
|
'type': 'danger',
|
|
},
|
|
}
|
|
action = tasks_with_report._get_send_report_action()
|
|
if not self.env.context.get('discard_logo_check') and self.env.is_admin() and not self.env.company.external_report_layout_id:
|
|
layout_action = self.env['ir.actions.report']._action_configure_external_report_layout(action)
|
|
action.pop('close_on_report_download', None)
|
|
return layout_action
|
|
return action
|
|
|
|
def action_preview_worksheet(self):
|
|
self.ensure_one()
|
|
source = 'fsm' if self.env.context.get('fsm_mode', False) else 'project'
|
|
return {
|
|
'type': 'ir.actions.act_url',
|
|
'target': 'self',
|
|
'url': self.get_portal_url(query_string=f'&source={source}')
|
|
}
|
|
|
|
def _message_post_after_hook(self, message, msg_vals):
|
|
if self.env.context.get('fsm_mark_as_sent') and not self.fsm_is_sent:
|
|
self.fsm_is_sent = True
|
|
return super()._message_post_after_hook(message, msg_vals)
|
|
|
|
# ---------------------------------------------------------
|
|
# Business Methods
|
|
# ---------------------------------------------------------
|
|
|
|
def _get_projects_to_make_billable_domain(self, additional_domain=None):
|
|
return Domain.AND([
|
|
super()._get_projects_to_make_billable_domain(additional_domain),
|
|
[('is_fsm', '=', False)],
|
|
])
|
|
|
|
def _allocated_hours_per_user_for_scale(self, users, start, stop):
|
|
fsm_tasks = self.filtered("is_fsm")
|
|
allocated_hours_mapped = super(ProjectTask, self - fsm_tasks)._allocated_hours_per_user_for_scale(users, start, stop)
|
|
users_work_intervals, dummy = users.sudo()._get_valid_work_intervals(start, stop)
|
|
for task in fsm_tasks:
|
|
# if the task goes over the gantt period, compute the duration only within
|
|
# the gantt period
|
|
max_start = max(start, pytz.utc.localize(task.planned_date_begin))
|
|
min_end = min(stop, pytz.utc.localize(task.date_deadline))
|
|
# for forecast tasks, use the conjunction between work intervals and task.
|
|
interval = Intervals([(
|
|
max_start, min_end, self.env['resource.calendar.attendance']
|
|
)])
|
|
duration = (task.date_deadline - task.planned_date_begin).total_seconds() / 3600.0 if task.planned_date_begin and task.date_deadline else 0.0
|
|
nb_hours_per_user = (sum_intervals(interval) / (len(task.user_ids) or 1)) if duration < 24 else 0.0
|
|
for user in task.user_ids:
|
|
if duration < 24:
|
|
allocated_hours_mapped[user.id] += nb_hours_per_user
|
|
else:
|
|
work_intervals = interval & users_work_intervals[user.id]
|
|
allocated_hours_mapped[user.id] += sum_intervals(work_intervals)
|
|
return allocated_hours_mapped
|
|
|
|
def action_fsm_view_overlapping_tasks(self):
|
|
action = super().action_fsm_view_overlapping_tasks()
|
|
if self.is_fsm:
|
|
action['context']['fsm_mode'] = True
|
|
action['domain'] = Domain.AND([
|
|
action['domain'],
|
|
[('is_fsm', '=', True)],
|
|
])
|
|
return action
|
|
|
|
def _prepare_domains_for_all_deadlines(self, date_start, date_end):
|
|
domain = super()._prepare_domains_for_all_deadlines(date_start, date_end)
|
|
if self.env.context.get('fsm_mode'):
|
|
domain['project'] = Domain.AND([
|
|
domain['project'],
|
|
[('is_fsm', '=', True)]
|
|
])
|
|
domain['milestone'] = Domain.AND([
|
|
domain['milestone'],
|
|
[('project_id.is_fsm', '=', True)]
|
|
])
|
|
return domain
|
|
|
|
def _get_report_base_filename(self):
|
|
self.ensure_one()
|
|
return 'Field Service Report %s - %s' % (self.name, self.partner_id.name)
|