175 lines
7.9 KiB
Python
175 lines
7.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
from datetime import timedelta, datetime
|
|
from collections import defaultdict
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.addons.resource.models.resource import Intervals
|
|
|
|
|
|
class MrpWorkcenter(models.Model):
|
|
_inherit = "mrp.workcenter"
|
|
|
|
equipment_ids = fields.One2many(
|
|
'maintenance.equipment', 'workcenter_id', string="Maintenance Equipment",
|
|
check_company=True)
|
|
|
|
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((schedule_date || '|' || schedule_date + INTERVAL '1h' * duration)) as date_intervals
|
|
FROM maintenance_request
|
|
LEFT JOIN maintenance_equipment
|
|
ON maintenance_request.equipment_id = maintenance_equipment.id
|
|
WHERE
|
|
schedule_date IS NOT NULL
|
|
AND duration IS NOT NULL
|
|
AND equipment_id IS NOT NULL
|
|
AND maintenance_equipment.workcenter_id IS NOT NULL
|
|
AND maintenance_equipment.workcenter_id IN %s
|
|
AND (schedule_date, schedule_date + INTERVAL '1h' * duration) OVERLAPS (%s, %s)
|
|
GROUP BY maintenance_equipment.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')] = [
|
|
[fields.Datetime.to_datetime(i) for i in intervals.split('|')]
|
|
for intervals in 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
|
|
|
|
expected_mtbf = fields.Integer(string='Expected MTBF', help='Expected Mean Time Between Failure')
|
|
mtbf = fields.Integer(compute='_compute_maintenance_request', string='MTBF', help='Mean Time Between Failure, computed based on done corrective maintenances.')
|
|
mttr = fields.Integer(compute='_compute_maintenance_request', string='MTTR', help='Mean Time To Repair')
|
|
estimated_next_failure = fields.Date(compute='_compute_maintenance_request', string='Estimated time before next failure (in days)', help='Computed as Latest Failure Date + MTBF')
|
|
latest_failure_date = fields.Date(compute='_compute_maintenance_request', string='Latest Failure Date')
|
|
workcenter_id = fields.Many2one(
|
|
'mrp.workcenter', string='Work Center', check_company=True)
|
|
|
|
@api.depends('effective_date', 'maintenance_ids.stage_id', 'maintenance_ids.close_date', 'maintenance_ids.request_date')
|
|
def _compute_maintenance_request(self):
|
|
for equipment in self:
|
|
maintenance_requests = equipment.maintenance_ids.filtered(lambda x: x.maintenance_type == 'corrective' and x.stage_id.done)
|
|
mttr_days = 0
|
|
for maintenance in maintenance_requests:
|
|
if maintenance.stage_id.done and maintenance.close_date:
|
|
mttr_days += (maintenance.close_date - maintenance.request_date).days
|
|
equipment.mttr = len(maintenance_requests) and (mttr_days / len(maintenance_requests)) or 0
|
|
maintenance = maintenance_requests.sorted(lambda x: x.request_date)
|
|
if len(maintenance) >= 1:
|
|
equipment.mtbf = (maintenance[-1].request_date - equipment.effective_date).days / len(maintenance)
|
|
equipment.latest_failure_date = maintenance and maintenance[-1].request_date or False
|
|
if equipment.mtbf:
|
|
equipment.estimated_next_failure = equipment.latest_failure_date + relativedelta(days=equipment.mtbf)
|
|
else:
|
|
equipment.estimated_next_failure = False
|
|
|
|
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)]")
|
|
|
|
|
|
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').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)]
|
|
}
|