From ff614544a41077843b3684e052f133d40b5477f6 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Wed, 6 Sep 2023 17:41:53 +0200 Subject: [PATCH] [IMP] maintenance_plan: Allow to generate generic plans for multiple equipments defined with a domain --- .../models/maintenance_equipment.py | 28 ++++++++++- maintenance_plan/models/maintenance_plan.py | 49 +++++++++++++++++++ maintenance_plan/readme/USAGE.rst | 4 ++ maintenance_plan/tests/__init__.py | 1 + maintenance_plan/tests/common.py | 9 ++++ .../tests/test_maintenance_plan.py | 35 +++++++++++++ .../tests/test_maintenance_plan_domain.py | 38 ++++++++++++++ .../views/maintenance_equipment_views.xml | 2 +- .../views/maintenance_plan_views.xml | 12 ++++- 9 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 maintenance_plan/tests/test_maintenance_plan_domain.py diff --git a/maintenance_plan/models/maintenance_equipment.py b/maintenance_plan/models/maintenance_equipment.py index aef2439af..2bd5984ec 100644 --- a/maintenance_plan/models/maintenance_equipment.py +++ b/maintenance_plan/models/maintenance_equipment.py @@ -20,6 +20,10 @@ class MaintenanceEquipment(models.Model): string="Maintenance Plan Count", store=True, ) + search_maintenance_plan_count = fields.Integer( + compute="_compute_search_maintenance_plan_count", + string="Maintenance All Plan Count", + ) maintenance_team_required = fields.Boolean(compute="_compute_team_required") notes = fields.Text(string="Notes") @@ -30,6 +34,15 @@ def _compute_maintenance_plan_count(self): equipment.with_context(active_test=False).maintenance_plan_ids ) + @api.depends("maintenance_plan_ids") + def _compute_search_maintenance_plan_count(self): + for equipment in self: + equipment.search_maintenance_plan_count = ( + self.env["maintenance.plan"] + .with_context(active_test=False) + .search_count([("search_equipment_id", "=", equipment.id)]) + ) + @api.depends("maintenance_plan_ids") def _compute_team_required(self): for equipment in self: @@ -55,6 +68,19 @@ def _check_company_id(self): ) ) + def _prepare_requests_from_plan(self, maintenance_plan, next_maintenance_date): + if self: + return self._prepare_request_from_plan( + maintenance_plan, next_maintenance_date + ) + equipments = maintenance_plan._get_maintenance_equipments() + return [ + equipment._prepare_request_from_plan( + maintenance_plan, next_maintenance_date + ) + for equipment in equipments + ] + def _prepare_request_from_plan(self, maintenance_plan, next_maintenance_date): team_id = maintenance_plan.maintenance_team_id.id or self.maintenance_team_id.id request_model = self.env["maintenance.request"] @@ -121,7 +147,7 @@ def _create_new_request(self, mtn_plan): # Create maintenance request until we reach planning horizon while next_maintenance_date <= horizon_date: if next_maintenance_date >= fields.Date.today(): - vals = self._prepare_request_from_plan(mtn_plan, next_maintenance_date) + vals = self._prepare_requests_from_plan(mtn_plan, next_maintenance_date) requests |= request_model.create(vals) next_maintenance_date = next_maintenance_date + mtn_plan.get_relativedelta( mtn_plan.interval, mtn_plan.interval_step or "year" diff --git a/maintenance_plan/models/maintenance_plan.py b/maintenance_plan/models/maintenance_plan.py index e093041d8..7cce1b605 100644 --- a/maintenance_plan/models/maintenance_plan.py +++ b/maintenance_plan/models/maintenance_plan.py @@ -6,6 +6,7 @@ from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError +from odoo.tools import safe_eval class MaintenancePlan(models.Model): @@ -81,6 +82,44 @@ class MaintenancePlan(models.Model): skip_notify_follower_on_requests = fields.Boolean( string="Do not notify to follower when creating requests?", default=True ) + generate_with_domain = fields.Boolean() + generate_domain = fields.Char(string="Apply on") + search_equipment_id = fields.Many2one( + comodel_name="maintenance.equipment", + compute="_compute_search_equipment", + search="_search_search_equipment", + ) + + @api.model + def _search_search_equipment(self, operator, value): + if operator != "=" or not value: + raise ValueError(_("Unsupported search operator")) + plans = self.search([("generate_with_domain", "=", True)]) + plan_ids = [] + equipment = self.env["maintenance.equipment"].browse(value) + for plan in plans: + if equipment.filtered_domain( + safe_eval.safe_eval( + plan.generate_domain or "[]", plan._get_eval_context() + ) + ): + plan_ids.append(plan.id) + return ["|", ("equipment_id", "=", value), ("id", "in", plan_ids)] + + @api.depends("equipment_id") + def _compute_search_equipment(self): + for record in self: + record.search_equipment_id = record.equipment_id + + def _get_eval_context(self): + """Prepare the context used when evaluating python code + :returns: dict -- evaluation context given to safe_eval + """ + return { + "datetime": safe_eval.datetime, + "dateutil": safe_eval.dateutil, + "time": safe_eval.time, + } def name_get(self): result = [] @@ -209,3 +248,13 @@ def button_manual_request_generation(self): for plan in self: equipment = plan.equipment_id equipment._create_new_request(plan) + + def _get_maintenance_equipments(self): + self.ensure_one() + if self.generate_with_domain and not self.equipment_id: + return self.env["maintenance.equipment"].search( + safe_eval.safe_eval( + self.generate_domain or "[]", self._get_eval_context() + ) + ) + return [self.equipment_id] diff --git a/maintenance_plan/readme/USAGE.rst b/maintenance_plan/readme/USAGE.rst index 7b327b062..f48436343 100644 --- a/maintenance_plan/readme/USAGE.rst +++ b/maintenance_plan/readme/USAGE.rst @@ -22,3 +22,7 @@ planning horizon. Therefore, the maintenance manager can have a proper planning of how many maintenance requests are programming for the future. Leaving planning horizon to 0 will only create those maintenance request that are scheduled for today. + +We can also create maintenance requests from a plan using a domain for selecting the equipments. +This way, we might have a single plan that will generate all the requests. +In order to use it, we need to mark the `Generate with Domain` field. diff --git a/maintenance_plan/tests/__init__.py b/maintenance_plan/tests/__init__.py index c701c6934..def774ebc 100644 --- a/maintenance_plan/tests/__init__.py +++ b/maintenance_plan/tests/__init__.py @@ -1 +1,2 @@ from . import test_maintenance_plan +from . import test_maintenance_plan_domain diff --git a/maintenance_plan/tests/common.py b/maintenance_plan/tests/common.py index a00707c3b..db3ea81e0 100644 --- a/maintenance_plan/tests/common.py +++ b/maintenance_plan/tests/common.py @@ -65,4 +65,13 @@ def setUpClass(cls): "planning_step": "month", } ) + cls.maintenance_plan_5 = cls.maintenance_plan_obj.create( + { + "start_maintenance_date": today, + "interval": 1, + "interval_step": "month", + "maintenance_plan_horizon": 2, + "planning_step": "month", + } + ) cls.report_obj = cls.env["ir.actions.report"] diff --git a/maintenance_plan/tests/test_maintenance_plan.py b/maintenance_plan/tests/test_maintenance_plan.py index 0d4aeb859..b2f4da56d 100644 --- a/maintenance_plan/tests/test_maintenance_plan.py +++ b/maintenance_plan/tests/test_maintenance_plan.py @@ -186,6 +186,41 @@ def test_generate_requests2(self): self.today_date + relativedelta(weeks=9), ) + def test_generate_requests_no_equipment(self): + self.cron.method_direct_trigger() + generated_requests = self.maintenance_request_obj.search( + [("maintenance_plan_id", "=", self.maintenance_plan_5.id)], + order="schedule_date asc", + ) + + self.assertEqual(len(generated_requests), 3) + + # We set plan start_maintenanca_date to a future one. New requests should take + # into account this new date. + + self.maintenance_plan_5.write( + { + "start_maintenance_date": fields.Date.to_string( + self.today_date + timedelta(weeks=9) + ), + "maintenance_plan_horizon": 3, + } + ) + + self.cron.method_direct_trigger() + + generated_requests = self.maintenance_request_obj.search( + [("maintenance_plan_id", "=", self.maintenance_plan_5.id)], + order="schedule_date asc", + ) + + self.assertEqual(len(generated_requests), 4) + self.assertEqual( + generated_requests[-1].request_date, + self.today_date + relativedelta(weeks=9), + ) + self.assertFalse(generated_requests.mapped("equipment_id")) + def test_get_relativedelta(self): plan = self.maintenance_plan_1 result = plan.get_relativedelta(1, "day") diff --git a/maintenance_plan/tests/test_maintenance_plan_domain.py b/maintenance_plan/tests/test_maintenance_plan_domain.py new file mode 100644 index 000000000..6e0578306 --- /dev/null +++ b/maintenance_plan/tests/test_maintenance_plan_domain.py @@ -0,0 +1,38 @@ +# Copyright 2023 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json + +from odoo.addons.maintenance_plan.tests.common import TestMaintenancePlanBase + + +class TestMaintenancePlanDomain(TestMaintenancePlanBase): + def test_generate_requests_no_domain(self): + self.cron.method_direct_trigger() + generated_requests = self.maintenance_request_obj.search( + [("maintenance_plan_id", "=", self.maintenance_plan_5.id)], + order="schedule_date asc", + ) + + self.assertEqual(len(generated_requests), 3) + self.assertFalse(generated_requests.mapped("equipment_id")) + + def test_generate_requests_domain(self): + equipment_2 = self.maintenance_equipment_obj.create({"name": "Laptop 2"}) + self.maintenance_plan_5.write( + { + "generate_with_domain": True, + "generate_domain": json.dumps( + [("id", "in", [equipment_2.id, self.equipment_1.id])] + ), + } + ) + self.cron.method_direct_trigger() + generated_requests = self.maintenance_request_obj.search( + [("maintenance_plan_id", "=", self.maintenance_plan_5.id)], + order="schedule_date asc", + ) + + self.assertEqual(len(generated_requests), 6) + self.assertIn(equipment_2, generated_requests.mapped("equipment_id")) + self.assertIn(self.equipment_1, generated_requests.mapped("equipment_id")) diff --git a/maintenance_plan/views/maintenance_equipment_views.xml b/maintenance_plan/views/maintenance_equipment_views.xml index ae101c23d..5fab3f277 100644 --- a/maintenance_plan/views/maintenance_equipment_views.xml +++ b/maintenance_plan/views/maintenance_equipment_views.xml @@ -15,7 +15,7 @@ > diff --git a/maintenance_plan/views/maintenance_plan_views.xml b/maintenance_plan/views/maintenance_plan_views.xml index f55ba270a..b8da209f9 100644 --- a/maintenance_plan/views/maintenance_plan_views.xml +++ b/maintenance_plan/views/maintenance_plan_views.xml @@ -67,6 +67,16 @@ name="maintenance_team_id" attrs="{'required': [('equipment_id', '=', False)]}" /> + + @@ -181,7 +191,7 @@ 'default_equipment_id': active_id, 'hide_equipment_id': 0 } ['|', ('active', '=', True), ('active', '=', - False), ('equipment_id', '=', active_id)] + False), ('search_equipment_id', '=', active_id)]