forked from Mapan/odoo17e
295 lines
13 KiB
Python
295 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import datetime
|
|
from dateutil.relativedelta import relativedelta
|
|
from collections import defaultdict
|
|
import pytz
|
|
|
|
from odoo import api, fields, models, Command, _
|
|
from odoo.addons.resource.models.utils import Intervals
|
|
from odoo.exceptions import UserError
|
|
|
|
|
|
class MaintenanceStage(models.Model):
|
|
_inherit = 'maintenance.stage'
|
|
|
|
create_leaves = fields.Boolean('Request Confirmed', default=True,
|
|
help="When this box is unticked, and the maintenance is of the type 'Work Center', no leave is created on the respective work center when a maintenance request is created.\n"
|
|
"If the box is ticked, the work center is automatically blocked for the listed duration, either at the specified date, or as soon as possible, if the work center is unavailable then.")
|
|
|
|
def write(self, vals):
|
|
res = super().write(vals)
|
|
if 'create_leaves' in vals:
|
|
maintenance_requests = self.env['maintenance.request'].search([('maintenance_for', '=', 'workcenter'), ('stage_id', 'in', self.ids)])
|
|
maintenance_requests._recreate_leaves()
|
|
return res
|
|
|
|
|
|
class MrpWorkcenter(models.Model):
|
|
_name = "mrp.workcenter"
|
|
_inherit = ["mrp.workcenter", 'maintenance.mixin', 'mail.thread', 'mail.activity.mixin']
|
|
|
|
equipment_ids = fields.One2many(
|
|
'maintenance.equipment', 'workcenter_id', string="Maintenance Equipment",
|
|
check_company=True)
|
|
maintenance_ids = fields.One2many('maintenance.request', 'workcenter_id', domain=[('maintenance_for', '=', 'workcenter')])
|
|
|
|
def _get_unavailability_intervals(self, start_datetime, end_datetime):
|
|
res = super(MrpWorkcenter, self)._get_unavailability_intervals(start_datetime, end_datetime)
|
|
if not self:
|
|
return res
|
|
sql = """
|
|
SELECT workcenter_id, ARRAY_AGG(ARRAY[schedule_date, schedule_date + INTERVAL '1h' * duration]) as date_intervals
|
|
FROM maintenance_request
|
|
WHERE maintenance_for = 'equipment'
|
|
AND schedule_date IS NOT NULL
|
|
AND duration IS NOT NULL
|
|
AND workcenter_id IN %s
|
|
AND (schedule_date, schedule_date + INTERVAL '1h' * duration) OVERLAPS (%s, %s)
|
|
GROUP BY workcenter_id
|
|
"""
|
|
self.env.cr.execute(sql, [tuple(self.ids), fields.Datetime.to_string(start_datetime.astimezone()), fields.Datetime.to_string(end_datetime.astimezone())])
|
|
res_maintenance = defaultdict(list)
|
|
for wc_row in self.env.cr.dictfetchall():
|
|
res_maintenance[wc_row.get('workcenter_id')] = wc_row.get('date_intervals')
|
|
|
|
for wc_id in self.ids:
|
|
intervals_previous_list = [(s.timestamp(), e.timestamp(), self.env['maintenance.request']) for s, e in res[wc_id]]
|
|
intervals_maintenances_list = [(m[0].timestamp(), m[1].timestamp(), self.env['maintenance.request']) for m in res_maintenance[wc_id]]
|
|
final_intervals_wc = Intervals(intervals_previous_list + intervals_maintenances_list)
|
|
res[wc_id] = [(datetime.fromtimestamp(s), datetime.fromtimestamp(e)) for s, e, _ in final_intervals_wc]
|
|
return res
|
|
|
|
|
|
class MaintenanceEquipment(models.Model):
|
|
_inherit = "maintenance.equipment"
|
|
_check_company_auto = True
|
|
|
|
workcenter_id = fields.Many2one(
|
|
'mrp.workcenter', string='Work Center', check_company=True)
|
|
|
|
def button_mrp_workcenter(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('work centers'),
|
|
'view_mode': 'form',
|
|
'res_model': 'mrp.workcenter',
|
|
'view_id': self.env.ref('mrp.mrp_workcenter_view').id,
|
|
'type': 'ir.actions.act_window',
|
|
'res_id': self.workcenter_id.id,
|
|
'context': {
|
|
'default_company_id': self.company_id.id
|
|
}
|
|
}
|
|
|
|
|
|
class MaintenanceRequest(models.Model):
|
|
_inherit = "maintenance.request"
|
|
_check_company_auto = True
|
|
|
|
production_id = fields.Many2one(
|
|
'mrp.production', string='Manufacturing Order', check_company=True)
|
|
workorder_id = fields.Many2one(
|
|
'mrp.workorder', string='Work Order', check_company=True)
|
|
production_company_id = fields.Many2one(string='Production Company', related='production_id.company_id')
|
|
company_id = fields.Many2one(domain="[('id', '=?', production_company_id)]")
|
|
maintenance_for = fields.Selection([
|
|
('equipment', 'Equipment'),
|
|
('workcenter', 'Work Center')],
|
|
string='For', default='equipment', required=True)
|
|
equipment_id = fields.Many2one(compute='_compute_equipment_id', store=True, readonly=False)
|
|
workcenter_id = fields.Many2one('mrp.workcenter', string='Work Center', compute='_compute_workcenter_id', store=True, readonly=False, check_company=True)
|
|
block_workcenter = fields.Boolean('Block Workcenter', help="It won't be possible to plan work orders or other maintenances on this workcenter during this time.")
|
|
recurring_leaves_count = fields.Integer('Additional Leaves to Plan Ahead', help='Block the workcenter for this many time slots in the future in advance.')
|
|
leave_ids = fields.Many2many('resource.calendar.leaves', string="Leaves")
|
|
|
|
@api.depends('maintenance_for')
|
|
def _compute_equipment_id(self):
|
|
self.filtered(lambda mr: mr.maintenance_for == 'workcenter' and mr.equipment_id).equipment_id = False
|
|
|
|
@api.depends('maintenance_for', 'equipment_id')
|
|
def _compute_workcenter_id(self):
|
|
for request in self:
|
|
if request.maintenance_for == 'equipment':
|
|
request.workcenter_id = request.equipment_id.workcenter_id
|
|
|
|
@api.depends('workcenter_id')
|
|
def _compute_maintenance_team_id(self):
|
|
for request in self:
|
|
if request.maintenance_for == 'workcenter':
|
|
request.maintenance_team_id = request.workcenter_id.maintenance_team_id
|
|
return super()._compute_maintenance_team_id()
|
|
|
|
@api.depends('workcenter_id')
|
|
def _compute_user_id(self):
|
|
for request in self:
|
|
if request.maintenance_for == 'workcenter':
|
|
request.user_id = request.workcenter_id.technician_user_id
|
|
return super()._compute_user_id()
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
allowed_to_raise = not self.id # self.copy() has an id, model.create() does not
|
|
res = super().create(vals_list)
|
|
res._recreate_leaves(raise_on_schedule_date_already_planned=allowed_to_raise) # do not raise when copying recurrent request
|
|
return res
|
|
|
|
def write(self, vals):
|
|
previous_create_leaves = {request.id: request.stage_id.create_leaves for request in self}
|
|
res = super().write(vals)
|
|
if 'leave_ids' not in vals and any(k in vals for k in ['workcenter_id', 'schedule_date', 'duration',
|
|
'maintenance_type',
|
|
'recurring_maintenance',
|
|
'repeat_interval', 'repeat_unit', 'repeat_type', 'repeat_until',
|
|
'block_workcenter',
|
|
'recurring_leaves_count']):
|
|
self._recreate_leaves()
|
|
elif 'stage_id' in vals:
|
|
self.filtered(lambda mr: mr.stage_id.create_leaves != previous_create_leaves[mr.id])._recreate_leaves()
|
|
return res
|
|
|
|
def unlink(self):
|
|
self.leave_ids.unlink()
|
|
return super().unlink()
|
|
|
|
def _need_new_activity(self, vals):
|
|
return super()._need_new_activity(vals) or vals.get('workcenter_id')
|
|
|
|
def _get_activity_note(self):
|
|
self.ensure_one()
|
|
if self.maintenance_for == 'workcenter':
|
|
return _(
|
|
'Request planned for %s',
|
|
self.workcenter_id._get_html_link()
|
|
)
|
|
return super()._get_activity_note()
|
|
|
|
def _recreate_leaves(self, raise_on_schedule_date_already_planned=True):
|
|
"""Allocate a new leave (and the early preventive ones) for the maintenance
|
|
based on schedule date and duration.
|
|
"""
|
|
self.leave_ids.unlink()
|
|
for request in self:
|
|
if request.archive:
|
|
continue
|
|
if request.maintenance_for != 'workcenter':
|
|
continue
|
|
if not request.schedule_date:
|
|
continue
|
|
if not request.workcenter_id:
|
|
raise UserError(_("The workcenter is missing for %s.", request.display_name))
|
|
if not request.block_workcenter:
|
|
continue
|
|
if request.stage_id.done or not request.stage_id.create_leaves:
|
|
continue
|
|
desired_date = request.schedule_date
|
|
date = max(desired_date, fields.Datetime.now())
|
|
duration = request.duration or 1
|
|
count = 1
|
|
if request.maintenance_type == 'preventive' and request.recurring_maintenance:
|
|
count += request.recurring_leaves_count
|
|
leave_ids_vals = []
|
|
for dummy in range(count):
|
|
from_date, to_date = request.workcenter_id._get_first_available_slot(date, duration * 60)
|
|
leave_ids_vals.append(Command.create({
|
|
'name': request.display_name,
|
|
'resource_id': request.workcenter_id.resource_id.id,
|
|
'calendar_id': request.workcenter_id.resource_calendar_id.id,
|
|
'date_from': from_date,
|
|
'date_to': to_date,
|
|
'time_type': 'leave',
|
|
}))
|
|
date += relativedelta(**{f"{request.repeat_unit}s": request.repeat_interval})
|
|
if request.repeat_type == 'until' and date.date() > request.repeat_until:
|
|
break
|
|
effective_date = leave_ids_vals[0][2]['date_from']
|
|
request.write({
|
|
'schedule_date': effective_date,
|
|
'duration': duration,
|
|
'leave_ids': leave_ids_vals,
|
|
})
|
|
if effective_date != desired_date:
|
|
user_tz = self.env.user.tz or self.env.context.get('tz')
|
|
user_pytz = pytz.timezone(user_tz) if user_tz else pytz.utc
|
|
text = _("The schedule has changed from %(desired_date)s to %(effective_date)s due to planned manufacturing orders.", desired_date=desired_date.astimezone(user_pytz), effective_date=effective_date.astimezone(user_pytz))
|
|
self.activity_schedule(
|
|
'mail.mail_activity_data_warning',
|
|
note=text
|
|
)
|
|
if raise_on_schedule_date_already_planned:
|
|
raise UserError("Manufacturing Orders are already scheduled for this time slot.")
|
|
|
|
def archive_equipment_request(self):
|
|
res = super().archive_equipment_request()
|
|
self.leave_ids.unlink()
|
|
self.write({'block_workcenter': False, 'recurring_leaves_count': 0})
|
|
return res
|
|
|
|
|
|
class MrpProduction(models.Model):
|
|
_inherit = "mrp.production"
|
|
|
|
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests")
|
|
request_ids = fields.One2many('maintenance.request', 'production_id')
|
|
|
|
@api.depends('request_ids')
|
|
def _compute_maintenance_count(self):
|
|
for production in self:
|
|
production.maintenance_count = len(production.request_ids)
|
|
|
|
def button_maintenance_req(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('New Maintenance Request'),
|
|
'view_mode': 'form',
|
|
'res_model': 'maintenance.request',
|
|
'type': 'ir.actions.act_window',
|
|
'context': {
|
|
'default_company_id': self.company_id.id,
|
|
'default_production_id': self.id,
|
|
},
|
|
'domain': [('production_id', '=', self.id)],
|
|
}
|
|
|
|
def open_maintenance_request_mo(self):
|
|
self.ensure_one()
|
|
action = {
|
|
'name': _('Maintenance Requests'),
|
|
'view_mode': 'kanban,tree,form,pivot,graph,calendar',
|
|
'res_model': 'maintenance.request',
|
|
'type': 'ir.actions.act_window',
|
|
'context': {
|
|
'default_company_id': self.company_id.id,
|
|
'default_production_id': self.id,
|
|
},
|
|
'domain': [('production_id', '=', self.id)],
|
|
}
|
|
if self.maintenance_count == 1:
|
|
production = self.env['maintenance.request'].search([('production_id', '=', self.id)])
|
|
action['view_mode'] = 'form'
|
|
action['res_id'] = production.id
|
|
return action
|
|
|
|
|
|
class MrpProductionWorkcenterLine(models.Model):
|
|
_inherit = "mrp.workorder"
|
|
|
|
def button_maintenance_req(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('New Maintenance Request'),
|
|
'view_mode': 'form',
|
|
'views': [(self.env.ref('mrp_maintenance.maintenance_request_view_form_inherit_mrp_workorder').id, 'form')],
|
|
'res_model': 'maintenance.request',
|
|
'type': 'ir.actions.act_window',
|
|
'context': {
|
|
'default_company_id': self.company_id.id,
|
|
'default_workorder_id': self.id,
|
|
'default_production_id': self.production_id.id,
|
|
'discard_on_footer_button': True,
|
|
},
|
|
'target': 'new',
|
|
'domain': [('workorder_id', '=', self.id)]
|
|
}
|