Files
enterprise-addons/mrp_maintenance/tests/test_mrp_maintenance.py
T

416 lines
21 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from datetime import datetime, timedelta
from freezegun import freeze_time
from odoo.tests import common, Form
class TestMrpMaintenance(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Relative models
cls.ResUsers = cls.env['res.users']
cls.equipment = cls.env['maintenance.equipment']
cls.workcenter = cls.env['mrp.workcenter']
# User references
cls.main_company = cls.env.ref('base.main_company')
calendar = cls.env['resource.calendar'].create({
'name': 'Main Company Calendar',
'company_id': cls.main_company.id,
'tz': 'UTC'
})
cls.main_company.resource_calendar_id = calendar
cls.technician_user_id = cls.env.ref('base.user_root')
cls.maintenance_team_id = cls.env.ref('maintenance.equipment_team_maintenance')
cls.stage_repaired_id = cls.env.ref('maintenance.stage_3').id
cls.stage_id = cls.env.ref('maintenance.stage_0').id
cls.category_id = cls.env['maintenance.equipment.category'].create({
'name': 'Monitors - Test',
'technician_user_id': cls.env.ref('base.user_admin').id,
'color': 3,
})
# Create user
cls.user = cls.ResUsers.create({
'name': "employee",
'company_id': cls.main_company.id,
'login': "employee",
'email': "employee@yourcompany.example.com",
'group_ids': [(6, 0, [cls.env.ref('base.group_user').id])]
})
# Create user with extra rights
cls.manager = cls.ResUsers.create({
'name': "Equipment Manager",
'company_id': cls.main_company.id,
'login': "manager",
'email': "eqmanager@yourcompany.example.com",
'group_ids': [(6, 0, [cls.env.ref('maintenance.group_equipment_manager').id])]
})
# Create workcenter
cls.workcenter_id = cls.env['mrp.workcenter'].create({
'name': 'Workcenter 01',
})
# Create method for create a maintenance request
def _create_request(self, name, request_date, equipment_id, maintenance_type):
values = {
'name': name,
'request_date': request_date,
'user_id': self.user.id,
'owner_user_id': self.user.id,
'equipment_id': equipment_id.id,
'maintenance_type': maintenance_type,
'stage_id': self.stage_id,
'maintenance_team_id': self.maintenance_team_id.id,
}
return self.env['maintenance.request'].create(values)
def _create_workcenter_request(self, name, request_date, workcenter_id, maintenance_type, **kwargs):
""" Create and return a workcenter maintenance request """
values = {
'name': name,
'request_date': request_date,
'user_id': self.technician_user_id.id,
'maintenance_for': 'workcenter',
'workcenter_id': workcenter_id.id,
'maintenance_type': maintenance_type,
'maintenance_team_id': self.maintenance_team_id.id,
'stage_id': self.stage_id,
**kwargs,
}
return self.env['maintenance.request'].create(values)
def test_00_mrp_maintenance(self):
""" In order to check Next preventive maintenance date"""
"""
ex: equipment = Acer Laptop
effective_date = 25-04-2018
period = 5
preventive maintenance date = effective date + period
30-04-2018 = 25-04-2018 + 5 days
create maintenance request
request_date = effective date + period
30-04-2018 = 25-04-2018 + 5 days
close maintenance request and calculate preventive maintenance date
close_date = 05-05-2018
preventive maintenance date = close_date + period
10-05-2018 = 05-05-2018 + 5day
"""
# Required for `assign_date` to be visible in the view
with self.debug_mode():
# Create a new equipment
equipment_form = Form(self.equipment)
equipment_form.name = 'Acer Laptop'
equipment_form.maintenance_team_id = self.maintenance_team_id
equipment_form.category_id = self.category_id
equipment_form.technician_user_id = self.technician_user_id
equipment_form.assign_date = time.strftime('%Y-%m-%d')
equipment_form.serial_no = 'MT/127/18291015'
equipment_form.expected_mtbf = 2
equipment_form.effective_date = (datetime.now().date() + timedelta(days=5)).strftime("%Y-%m-%d")
equipment_01 = equipment_form.save()
# Check that equipment is created or not
self.assertTrue(equipment_01, 'Equipment not created')
# Create a maintenance request
maintenance_request_01 = self._create_request(name='Display not working', request_date=datetime.now().date() + timedelta(days=10), equipment_id=equipment_01, maintenance_type="preventive")
# check that maintenance_request is created or not
self.assertTrue(maintenance_request_01, 'Maintenance Request not created')
# check maintenance request date.
self.assertEqual(maintenance_request_01.request_date, datetime.now().date() + timedelta(days=10), 'maintenance request_date is wrong')
# Updating maintenance to Done stage and its close date
maintenance_request_01.write({'stage_id': self.stage_repaired_id})
maintenance_request_01.close_date = datetime.now().date() + timedelta(days=15)
# Create another request which would be in maintenance todo stage
maintenance_request_02 = self._create_request(name='Display not working', request_date=datetime.now().date() + timedelta(days=25), equipment_id=equipment_01, maintenance_type="preventive")
# check that maintenance_request is created or not
self.assertTrue(maintenance_request_02, 'Maintenance Request not created')
def test_01_mrp_maintenance(self):
""" In order to check MTBF,MTTR,estimated next failure and estimated
latest failure equipment requests.
"""
# Required for `assign_date` to be visible in the view
with self.debug_mode():
# Create a new equipment
equipment_form = Form(self.equipment)
equipment_form.name = 'Acer Laptop'
equipment_form.maintenance_team_id = self.maintenance_team_id
equipment_form.category_id = self.category_id
equipment_form.technician_user_id = self.technician_user_id
equipment_form.assign_date = time.strftime('%Y-%m-%d')
equipment_form.serial_no = 'MT/127/18291015'
equipment_form.expected_mtbf = 2
equipment_form.effective_date = '2017-04-13'
equipment_01 = equipment_form.save()
# Check that equipment is created or not
self.assertTrue(equipment_01, 'Equipment not created')
# Create maintenance requests
# Maintenance Request Request Date
# 1) 2017-05-03
# 2) 2017-05-23
# 3) 2017-06-11
maintenance_request_01 = self._create_request(name='Some keys are not working', request_date=datetime(2017, 5, 3).date(), equipment_id=equipment_01, maintenance_type="corrective")
maintenance_request_02 = self._create_request(name='Touchpad not working', request_date=datetime(2017, 5, 23).date(), equipment_id=equipment_01, maintenance_type="corrective")
maintenance_request_03 = self._create_request(name='Battery drains fast', request_date=datetime(2017, 6, 11).date(), equipment_id=equipment_01, maintenance_type="corrective")
# check that maintenance_request is created or not
self.assertTrue(maintenance_request_01, 'Maintenance Request not created')
self.assertTrue(maintenance_request_02, 'Maintenance Request not created')
self.assertTrue(maintenance_request_03, 'Maintenance Request not created')
# Request Request Date Close Date diff_days
# 1) 2017-05-03 2017-05-13 10
# 2) 2017-05-23 2017-05-28 5
# 3) 2017-06-11 2017-06-11 0
# MTTR = Day used to handle maintenance request / No of request
# 5 = (10+5+0)15 / 3
# MTBF = Gap in days of between effective date and last request / No of request
# 19 = (2017-06-11 - 2017-04-13) 59 / 3
# estimated next failure = latest failure date + MTBF
# 2017-06-30 00:00:00 = 2017-06-11 + 19
# maintenance_request_01 write stage_id and close_date.
maintenance_request_01.write({'stage_id': self.stage_repaired_id})
maintenance_request_01.close_date = datetime(2017, 5, 3).date() + timedelta(days=10)
self.assertEqual(maintenance_request_01.close_date, datetime(2017, 5, 13).date(), 'Wrong close date on maintenance request.')
# maintenance_request_02 write stage_id and close_date.
maintenance_request_02.write({'stage_id': self.stage_repaired_id})
maintenance_request_02.close_date = datetime(2017, 5, 23).date() + timedelta(days=5)
self.assertEqual(maintenance_request_02.close_date, datetime(2017, 5, 28).date(), 'Wrong close date on maintenance request.')
# maintenance_request_03 write stage_id and close_date.
maintenance_request_03.write({'stage_id': self.stage_repaired_id})
maintenance_request_03.close_date = maintenance_request_03.request_date
self.assertEqual(maintenance_request_03.close_date, datetime(2017, 6, 11).date(), 'Wrong close date on maintenance request.')
# Check MTTR = Day used to handle maintenance request / No of request (15 / 3)
self.assertEqual(equipment_01.mttr, 5, 'Maintenance Equipment MTTR(Mean Time To Repair) should be 5 days')
# Check MTBF = Gap in days of between effective date and last request / No of request
self.assertEqual(equipment_01.mtbf, 19, 'Maintenance Equipment MTBF(Mean Time Between Failure) should be 19 days')
# Check calculation of latest failure date (should be 11-06-2017)
latest_failure_date = equipment_01.latest_failure_date
self.assertEqual(maintenance_request_03.request_date, datetime(2017, 6, 11).date(), 'Wrong request_date on maintenance request.')
self.assertEqual(latest_failure_date, maintenance_request_03.request_date, 'Wrong latest_failure_date on maintenance request.')
# Check calculation of estimated next failure (should be 30-06-2017)
# Step-1: latest failure date + MTBF
estimated_next_failure = equipment_01.latest_failure_date + timedelta(days=equipment_01.mtbf)
self.assertEqual(estimated_next_failure, datetime(2017, 6, 30).date(), 'Wrong latest_failure_date on maintenance request.')
def test_01_mrp_maintenance_workcenter(self): # adapted from test_01_mrp_maintenance (working on equipment)
# Required for `assign_date` to be visible in the view
with self.debug_mode():
# Create a new workcenter
workcenter_form = Form(self.workcenter, view='mrp_maintenance.mrp_workcenter_view_form_inherit_maintenance')
workcenter_form.name = 'WorkCenter'
workcenter_form.maintenance_team_id = self.maintenance_team_id
workcenter_form.technician_user_id = self.technician_user_id
workcenter_form.expected_mtbf = 2
workcenter_form.effective_date = '2017-04-13'
workcenter = workcenter_form.save()
# Check that workcenter is createdworkcenter
self.assertTrue(workcenter, 'Equipment not created')
# Create maintenance requests
# Maintenance Request Request Date
# 1) 2017-05-03
# 2) 2017-05-23
# 3) 2017-06-11
maintenance_request_1 = self._create_workcenter_request(name='Some keys are not working', request_date=datetime(2017, 5, 3).date(), workcenter_id=workcenter, maintenance_type="corrective")
maintenance_request_2 = self._create_workcenter_request(name='Touchpad not working', request_date=datetime(2017, 5, 23).date(), workcenter_id=workcenter, maintenance_type="corrective")
maintenance_request_3 = self._create_workcenter_request(name='Battery drains fast', request_date=datetime(2017, 6, 11).date(), workcenter_id=workcenter, maintenance_type="corrective")
# check that maintenance_requests are created or not
self.assertTrue(maintenance_request_1, 'Maintenance Request not created')
self.assertTrue(maintenance_request_2, 'Maintenance Request not created')
self.assertTrue(maintenance_request_3, 'Maintenance Request not created')
# Request Request Date Close Date diff_days
# 1) 2017-05-03 2017-05-13 10
# 2) 2017-05-23 2017-05-28 5
# 3) 2017-06-11 2017-06-11 0
# MTTR = Day used to handle maintenance request / No of request
# 5 = (10+5+0)15 / 3
# MTBF = Gap in days of between effective date and last request / No of request
# 19 = (2017-06-11 - 2017-04-13) 59 / 3
# estimated next failure = latest failure date + MTBF
# 2017-06-30 00:00:00 = 2017-06-11 + 19
# maintenance_request_1 write stage_id and close_date.
maintenance_request_1.write({'stage_id': self.stage_repaired_id})
maintenance_request_1.close_date = datetime(2017, 5, 3).date() + timedelta(days=10)
self.assertEqual(maintenance_request_1.close_date, datetime(2017, 5, 13).date(), 'Wrong close date on maintenance request.')
# maintenance_request_2 write stage_id and close_date.
maintenance_request_2.write({'stage_id': self.stage_repaired_id})
maintenance_request_2.close_date = datetime(2017, 5, 23).date() + timedelta(days=5)
self.assertEqual(maintenance_request_2.close_date, datetime(2017, 5, 28).date(), 'Wrong close date on maintenance request.')
# maintenance_request_3 write stage_id and close_date.
maintenance_request_3.write({'stage_id': self.stage_repaired_id})
maintenance_request_3.close_date = maintenance_request_3.request_date
self.assertEqual(maintenance_request_3.close_date, datetime(2017, 6, 11).date(), 'Wrong close date on maintenance request.')
# Check MTTR = Day used to handle maintenance request / No of request (15 / 3)
self.assertEqual(workcenter.mttr, 5, 'Maintenance Equipment MTTR(Mean Time To Repair) should be 5 days')
# Check MTBF = Gap in days of between effective date and last request / No of request
self.assertEqual(workcenter.mtbf, 19, 'Maintenance Equipment MTBF(Mean Time Between Failure) should be 19 days')
# Check calculation of latest failure date (should be 11-06-2017)
latest_failure_date = workcenter.latest_failure_date
self.assertEqual(maintenance_request_3.request_date, datetime(2017, 6, 11).date(), 'Wrong request_date on maintenance request.')
self.assertEqual(latest_failure_date, maintenance_request_3.request_date, 'Wrong latest_failure_date on maintenance request.')
# Check calculation of estimated next failure (should be 30-06-2017)
# Step-1: latest failure date + MTBF
estimated_next_failure = workcenter.latest_failure_date + timedelta(days=workcenter.mtbf)
self.assertEqual(estimated_next_failure, datetime(2017, 6, 30).date(), 'Wrong latest_failure_date on maintenance request.')
def test_workcenter_unavailability(self):
# Required for `assign_date` to be visible in the view
self.env.user.group_ids += self.env.ref('mrp.group_mrp_routings')
with self.debug_mode():
# Create a new equipment
equipment_form = Form(self.equipment)
equipment_form.name = 'Screwdriver'
equipment_form.maintenance_team_id = self.maintenance_team_id
equipment_form.category_id = self.category_id
equipment_form.technician_user_id = self.technician_user_id
equipment_form.assign_date = time.strftime('%Y-%m-%d')
equipment_form.serial_no = 'MT/127/18291015'
equipment_form.expected_mtbf = 2
equipment_form.effective_date = datetime.now().date() + timedelta(days=5)
equipment = equipment_form.save()
maintenance_request_01 = self._create_request(name='Does not turn', request_date=datetime(2017, 5, 3).date(), equipment_id=equipment, maintenance_type="corrective")
maintenance_request_01.write({"schedule_date": datetime(2017, 5, 3, 8, microsecond=500),
"schedule_end": datetime(2017, 5, 3, 10, microsecond=500), "workcenter_id": self.workcenter_id.id})
start_datetime = datetime(2017, 5, 3, 7)
intervals_by_workcenter = self.workcenter_id._get_unavailability_intervals(start_datetime, start_datetime + timedelta(hours=4))
intervals = intervals_by_workcenter[self.workcenter_id.id]
# We will have two unavailabilities for the requested timeframe:
# - From 7 to 8 -> outside of working hours (according to calendar)
# - From 8:500 to 10:500 -> scheduled maintenance
self.assertEqual(len(intervals), 2)
self.assertListEqual(
intervals, [
(datetime(2017, 5, 3, 7), datetime(2017, 5, 3, 8)),
(datetime(2017, 5, 3, 8, microsecond=500), datetime(2017, 5, 3, 10, microsecond=500))
]
)
def test_maintenance_team_id_compute(self):
""" Ensure that the maintenance request does not update its maintenance_team_id
when changing to a workcenter that does not have a maintenance_team_id.
"""
workcenter_without_team = self.env['mrp.workcenter'].create({
'name': 'Workcenter No Team',
})
request = self._create_workcenter_request(
name='Unexpected shutdowns',
request_date=datetime(2018, 4, 5).date(),
workcenter_id=workcenter_without_team,
maintenance_type="corrective"
)
request.write({'workcenter_id': self.workcenter_id.id})
self.assertEqual(
request.maintenance_team_id.id,
self.maintenance_team_id.id,
"Maintenance team should remain unchanged when workcenter has no maintenance_team_id."
)
@freeze_time("2025-05-21 11:00:00")
def test_maintenance_block_workcenter(self):
"""
Validate that maintenance requests can create resource leaves as long as they do not
conflict with existing workorder intervals, regardless of working hours (unlike workorder scheduling).
Also verifies that recurring maintenance requests are properly rescheduled after any overlapping
manufacturing orders have been completed.
"""
product = self.env['product.product'].create({'name': 'Test Product'})
bom = self.env['mrp.bom'].create({'product_tmpl_id': product.product_tmpl_id.id})
self.env['mrp.routing.workcenter'].create({
'name': 'Test Operation',
'workcenter_id': self.workcenter_id.id,
'bom_id': bom.id,
})
mo = self.env['mrp.production'].create({'bom_id': bom.id, 'date_start': datetime.now()}) # 11:00 AM - 12:00 PM
mo.action_confirm()
mo.button_plan()
wo_finished_date = mo.workorder_ids[0].date_finished
# Create a preventive MR scheduled for one day before today (past), from 07:00 to 18:00.
schedule_date = datetime(2025, 5, 20, 7, 0, 0)
mr = self._create_workcenter_request(
name="Preventive Maintenance (Block Workcenter)",
request_date=schedule_date,
workcenter_id=self.workcenter_id,
maintenance_type='preventive',
schedule_date=schedule_date,
schedule_end=datetime(2025, 5, 20, 18, 0, 0),
recurring_maintenance=True,
repeat_unit='day',
repeat_interval=1,
repeat_until=schedule_date + timedelta(weeks=1),
recurring_leaves_count=2,
block_workcenter=True,
)
# Ensure the MR is allowed to span non-working hours (e.g., 07:00-08:00, 13:00-14:00)
# and that leave creation in the past is permitted without blocking the user.
self.assertEqual(len(mr.leave_ids), 3) # 1 for today + 2 recurring leaves
self.assertRecordValues(mr.leave_ids, [
{'date_from': datetime(2025, 5, 20, 7, 0, 0), 'date_to': datetime(2025, 5, 20, 18, 0, 0)},
{'date_from': wo_finished_date, 'date_to': wo_finished_date + timedelta(hours=11)},
{'date_from': datetime(2025, 5, 22, 7, 0, 0), 'date_to': datetime(2025, 5, 22, 18, 0, 0)},
])
mr.write({'stage_id': self.stage_repaired_id})
next_mr = self.env['maintenance.request'].search([('id', '!=', mr.id), ('name', '=', mr.name)])
# Confirm that the next MR is scheduled after the MO has finished
self.assertEqual(next_mr.schedule_date, wo_finished_date,
"Next preventive maintenance should be scheduled after the MO's end.")