From 6fd5595e0e053cce23922d852e1ebbdaf7771cfb Mon Sep 17 00:00:00 2001 From: Morita Shinnosuke Date: Fri, 10 Jul 2026 01:37:46 +0000 Subject: [PATCH 1/4] [6788][ADD] maintenance_equipment_usability --- maintenance_equipment_usability/README.rst | 96 ++++ maintenance_equipment_usability/__init__.py | 4 + .../__manifest__.py | 20 + maintenance_equipment_usability/i18n/ja.po | 191 ++++++++ .../models/__init__.py | 7 + .../models/maintenance_equipment.py | 148 ++++++ .../models/maintenance_request.py | 49 ++ .../models/res_company.py | 15 + .../models/res_config_settings.py | 16 + .../pyproject.toml | 3 + .../readme/CONTRIBUTORS.md | 2 + .../readme/DESCRIPTION.md | 3 + .../readme/USAGE.md | 18 + .../static/description/index.html | 446 ++++++++++++++++++ .../tests/__init__.py | 4 + .../test_maintenance_equipment_usability.py | 135 ++++++ .../views/maintenance_equipment_views.xml | 106 +++++ .../views/maintenance_request_views.xml | 69 +++ .../views/res_config_settings_views.xml | 23 + 19 files changed, 1355 insertions(+) create mode 100644 maintenance_equipment_usability/README.rst create mode 100644 maintenance_equipment_usability/__init__.py create mode 100644 maintenance_equipment_usability/__manifest__.py create mode 100644 maintenance_equipment_usability/i18n/ja.po create mode 100644 maintenance_equipment_usability/models/__init__.py create mode 100644 maintenance_equipment_usability/models/maintenance_equipment.py create mode 100644 maintenance_equipment_usability/models/maintenance_request.py create mode 100644 maintenance_equipment_usability/models/res_company.py create mode 100644 maintenance_equipment_usability/models/res_config_settings.py create mode 100644 maintenance_equipment_usability/pyproject.toml create mode 100644 maintenance_equipment_usability/readme/CONTRIBUTORS.md create mode 100644 maintenance_equipment_usability/readme/DESCRIPTION.md create mode 100644 maintenance_equipment_usability/readme/USAGE.md create mode 100644 maintenance_equipment_usability/static/description/index.html create mode 100644 maintenance_equipment_usability/tests/__init__.py create mode 100644 maintenance_equipment_usability/tests/test_maintenance_equipment_usability.py create mode 100644 maintenance_equipment_usability/views/maintenance_equipment_views.xml create mode 100644 maintenance_equipment_usability/views/maintenance_request_views.xml create mode 100644 maintenance_equipment_usability/views/res_config_settings_views.xml diff --git a/maintenance_equipment_usability/README.rst b/maintenance_equipment_usability/README.rst new file mode 100644 index 0000000..1e95b3a --- /dev/null +++ b/maintenance_equipment_usability/README.rst @@ -0,0 +1,96 @@ +=============================== +Maintenance Equipment Usability +=============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:f595baf37a394c7841ac7376a7cf30d849fd9465b5e319b3b23d1d3d444c4a71 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-qrtl%2Frbkk--custom-lightgray.png?logo=github + :target: https://github.com/qrtl/rbkk-custom/tree/18.0/maintenance_equipment_usability + :alt: qrtl/rbkk-custom + +|badge1| |badge2| |badge3| + +This module adds an equipment status and a pass/fail maintenance result, +and computes equipment usability from the status, the latest completed +maintenance request result, and a configurable usability grace period. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +The equipment Status (Preparing, Operating, Idle, Disposal / Scrapped) +gates usability: only equipment in the Operating status can be usable. +In any other status the equipment is unusable regardless of its +maintenance result. + +When a maintenance request is completed (it reaches a done stage), set +its Maintenance Result to Passed or Failed. For equipment in the +Operating status, usability is then computed from the latest completed +request by completion date: + +- Failed result: the equipment is unusable. +- Passed result, within the grace period: the equipment is usable. +- Passed result, past the grace period: the equipment is unusable. +- No result yet: the equipment usability is unknown. + +The grace period runs from the latest completed maintenance result date +and defaults to 12 months. Configure it in Settings > Maintenance > +Usability Grace Period (for example 13 or 14 months). Usability is +evaluated from the current date, so equipment becomes unusable +automatically once the grace period elapses. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Quartile + +Contributors +------------ + +- `Quartile `__: + + - Shinnosuke Morita + +Maintainers +----------- + +.. |maintainer-smorita7749| image:: https://github.com/smorita7749.png?size=40px + :target: https://github.com/smorita7749 + :alt: smorita7749 + +Current maintainer: + +|maintainer-smorita7749| + +This module is part of the `qrtl/rbkk-custom `_ project on GitHub. + +You are welcome to contribute. diff --git a/maintenance_equipment_usability/__init__.py b/maintenance_equipment_usability/__init__.py new file mode 100644 index 0000000..116bc48 --- /dev/null +++ b/maintenance_equipment_usability/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import models diff --git a/maintenance_equipment_usability/__manifest__.py b/maintenance_equipment_usability/__manifest__.py new file mode 100644 index 0000000..f3d2c1f --- /dev/null +++ b/maintenance_equipment_usability/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +{ + "name": "Maintenance Equipment Usability", + "version": "18.0.1.0.0", + "category": "Maintenance", + "summary": "Compute equipment usability from maintenance request results", + "author": "Quartile", + "website": "https://www.quartile.co", + "maintainers": ["smorita7749"], + "license": "LGPL-3", + "depends": ["maintenance"], + "data": [ + "views/res_config_settings_views.xml", + "views/maintenance_request_views.xml", + "views/maintenance_equipment_views.xml", + ], + "installable": True, +} diff --git a/maintenance_equipment_usability/i18n/ja.po b/maintenance_equipment_usability/i18n/ja.po new file mode 100644 index 0000000..dcf7af1 --- /dev/null +++ b/maintenance_equipment_usability/i18n/ja.po @@ -0,0 +1,191 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * maintenance_equipment_usability +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-07-10 01:09+0000\n" +"PO-Revision-Date: 2026-07-10 01:09+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: maintenance_equipment_usability +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.res_config_settings_view_form +msgid "months" +msgstr "" + +#. module: maintenance_equipment_usability +#: model:ir.model,name:maintenance_equipment_usability.model_res_company +msgid "Companies" +msgstr "会社" + +#. module: maintenance_equipment_usability +#: model:ir.model,name:maintenance_equipment_usability.model_res_config_settings +msgid "Config Settings" +msgstr "コンフィグ設定" + +#. module: maintenance_equipment_usability +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.hr_equipment_request_view_form +msgid "Create Follow-up Request" +msgstr "フォローアップ依頼を作成" + +#. module: maintenance_equipment_usability +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.res_config_settings_view_form +msgid "Equipment Usability" +msgstr "設備使用可否" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_request__maintenance_result__failed +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.hr_equipment_request_view_search +msgid "Failed" +msgstr "不合格" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,help:maintenance_equipment_usability.field_maintenance_request__source_request_id +msgid "Failed request this follow-up request was created from." +msgstr "このフォローアップ依頼の作成元となった不合格の整備依頼。" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_request__followup_request_ids +msgid "Follow-up Requests" +msgstr "フォローアップ依頼" + +#. module: maintenance_equipment_usability +#. odoo-python +#: code:addons/maintenance_equipment_usability/models/maintenance_request.py:0 +msgid "Follow-up of %s" +msgstr "%s のフォローアップ" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__status__idle +msgid "Idle" +msgstr "遊休" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_equipment__latest_maintenance_result_date +msgid "Latest Result Date" +msgstr "最新結果日" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_equipment__latest_maintenance_result_request_id +msgid "Latest Result Request" +msgstr "最新結果の整備依頼" + +#. module: maintenance_equipment_usability +#: model:ir.model,name:maintenance_equipment_usability.model_maintenance_equipment +msgid "Maintenance Equipment" +msgstr "整備設備" + +#. module: maintenance_equipment_usability +#: model:ir.model,name:maintenance_equipment_usability.model_maintenance_request +msgid "Maintenance Request" +msgstr "整備依頼" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_request__maintenance_result +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.hr_equipment_request_view_search +msgid "Maintenance Result" +msgstr "整備結果" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_request__maintenance_result__none +msgid "No Result" +msgstr "結果なし" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,help:maintenance_equipment_usability.field_res_company__usability_grace_period_months +#: model:ir.model.fields,help:maintenance_equipment_usability.field_res_config_settings__usability_grace_period_months +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.res_config_settings_view_form +msgid "" +"Number of months after the latest completed maintenance result during which " +"the equipment stays usable." +msgstr "最新の完了した整備結果から、設備が使用可能であり続ける月数。" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__status__operating +msgid "Operating" +msgstr "稼働中" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_request__maintenance_result__passed +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.hr_equipment_request_view_search +msgid "Passed" +msgstr "合格" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__status__preparing +msgid "Preparing" +msgstr "準備中" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__status__retired +msgid "Retired" +msgstr "除却" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__status__scrapped +msgid "Scrapped" +msgstr "廃棄" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_equipment__smart_search +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_request__smart_search +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_res_company__smart_search +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_res_config_settings__smart_search +msgid "Smart Search" +msgstr "スマート検索" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_request__source_request_id +msgid "Source Request" +msgstr "作成元の整備依頼" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_equipment__status +msgid "Status" +msgstr "ステータス" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__usability_state__unknown +msgid "Unknown" +msgstr "不明" + +#. module: maintenance_equipment_usability +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.hr_equipment_view_search +msgid "Unknown Usability" +msgstr "使用可否不明" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__usability_state__unusable +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.hr_equipment_view_search +msgid "Unusable" +msgstr "使用不可" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_maintenance_equipment__usability_state +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.hr_equipment_view_form +msgid "Usability" +msgstr "使用可否" + +#. module: maintenance_equipment_usability +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.res_config_settings_view_form +msgid "Usability Grace Period" +msgstr "猶予期間" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_res_company__usability_grace_period_months +#: model:ir.model.fields,field_description:maintenance_equipment_usability.field_res_config_settings__usability_grace_period_months +msgid "Usability Grace Period (Months)" +msgstr "猶予期間(月)" + +#. module: maintenance_equipment_usability +#: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__usability_state__usable +#: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.hr_equipment_view_search +msgid "Usable" +msgstr "使用可" diff --git a/maintenance_equipment_usability/models/__init__.py b/maintenance_equipment_usability/models/__init__.py new file mode 100644 index 0000000..822b3bc --- /dev/null +++ b/maintenance_equipment_usability/models/__init__.py @@ -0,0 +1,7 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import maintenance_request +from . import maintenance_equipment +from . import res_company +from . import res_config_settings diff --git a/maintenance_equipment_usability/models/maintenance_equipment.py b/maintenance_equipment_usability/models/maintenance_equipment.py new file mode 100644 index 0000000..4a27704 --- /dev/null +++ b/maintenance_equipment_usability/models/maintenance_equipment.py @@ -0,0 +1,148 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models + + +class MaintenanceEquipment(models.Model): + _inherit = "maintenance.equipment" + + status = fields.Selection( + selection=[ + ("preparing", "Preparing"), + ("operating", "Operating"), + ("idle", "Idle"), + ("retired", "Retired"), + ("scrapped", "Scrapped"), + ], + default="preparing", + ) + usability_state = fields.Selection( + selection=[ + ("unknown", "Unknown"), + ("usable", "Usable"), + ("unusable", "Unusable"), + ], + compute="_compute_usability", + search="_search_usability_state", + string="Usability", + ) + latest_maintenance_result_request_id = fields.Many2one( + comodel_name="maintenance.request", + compute="_compute_latest_maintenance_result", + store=True, + readonly=True, + string="Latest Result Request", + ) + latest_maintenance_result_date = fields.Date( + compute="_compute_latest_maintenance_result", + store=True, + readonly=True, + string="Latest Result Date", + ) + + @api.depends( + "maintenance_ids.close_date", + "maintenance_ids.request_date", + "maintenance_ids.stage_id.done", + "maintenance_ids.maintenance_result", + "maintenance_ids.archive", + ) + def _compute_latest_maintenance_result(self): + requests = ( + self.env["maintenance.request"] + .sudo() + .search( + [ + ("equipment_id", "in", self.ids), + ("stage_id.done", "=", True), + ("maintenance_result", "in", ("passed", "failed")), + ("archive", "=", False), + ], + order="equipment_id, close_date desc, request_date desc, id desc", + ) + ) + latest_by_equipment = {} + for request in requests: + latest_by_equipment.setdefault(request.equipment_id.id, request) + for equipment in self: + request = latest_by_equipment.get(equipment.id) + equipment.latest_maintenance_result_request_id = request + equipment.latest_maintenance_result_date = ( + (request.close_date or request.request_date) if request else False + ) + + @api.depends( + "status", + "latest_maintenance_result_request_id.maintenance_result", + "latest_maintenance_result_date", + ) + def _compute_usability(self): + grace_months = self.env.company.usability_grace_period_months + today = fields.Date.context_today(self) + for equipment in self: + if equipment.status != "operating": + equipment.usability_state = "unusable" + continue + request = equipment.latest_maintenance_result_request_id + result_date = equipment.latest_maintenance_result_date + if not request or not result_date: + equipment.usability_state = "unknown" + continue + if request.maintenance_result == "failed": + equipment.usability_state = "unusable" + elif today > result_date + relativedelta(months=grace_months): + equipment.usability_state = "unusable" + else: + equipment.usability_state = "usable" + + def _search_usability_state(self, operator, value): + all_states = ("unknown", "usable", "unusable") + if operator == "=": + states = [value] if value in all_states else [] + elif operator == "!=": + states = [s for s in all_states if s != value] + elif operator == "in": + states = [s for s in value if s in all_states] + elif operator == "not in": + states = [s for s in all_states if s not in value] + else: + states = [] + if not states: + return [("id", "=", False)] + result = "latest_maintenance_result_request_id.maintenance_result" + threshold = fields.Date.context_today(self) - relativedelta( + months=self.env.company.usability_grace_period_months + ) + domains = { + # unknown: operating, but no completed result yet + "unknown": [ + "&", + ("status", "=", "operating"), + ("latest_maintenance_result_request_id", "=", False), + ], + # usable: operating, latest result passed, within the grace period + "usable": [ + "&", + ("status", "=", "operating"), + "&", + (result, "=", "passed"), + ("latest_maintenance_result_date", ">=", threshold), + ], + # unusable: not operating, or result failed, or passed but past grace + "unusable": [ + "|", + ("status", "!=", "operating"), + "|", + (result, "=", "failed"), + "&", + (result, "=", "passed"), + ("latest_maintenance_result_date", "<", threshold), + ], + } + domain = domains[states[0]] + for state in states[1:]: + domain = ["|"] + domain + domains[state] + return domain diff --git a/maintenance_equipment_usability/models/maintenance_request.py b/maintenance_equipment_usability/models/maintenance_request.py new file mode 100644 index 0000000..1a69eae --- /dev/null +++ b/maintenance_equipment_usability/models/maintenance_request.py @@ -0,0 +1,49 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import _, fields, models + + +class MaintenanceRequest(models.Model): + _inherit = "maintenance.request" + + maintenance_result = fields.Selection( + selection=[ + ("none", "No Result"), + ("passed", "Passed"), + ("failed", "Failed"), + ], + default="none", + copy=False, + tracking=True, + ) + source_request_id = fields.Many2one( + comodel_name="maintenance.request", + string="Source Request", + copy=False, + readonly=True, + help="Failed request this follow-up request was created from.", + ) + followup_request_ids = fields.One2many( + comodel_name="maintenance.request", + inverse_name="source_request_id", + string="Follow-up Requests", + ) + + def action_create_followup_request(self): + self.ensure_one() + followup = self.env["maintenance.request"].create( + { + "name": _("Follow-up of %s", self.name), + "equipment_id": self.equipment_id.id, + "maintenance_type": "corrective", + "source_request_id": self.id, + } + ) + return { + "type": "ir.actions.act_window", + "res_model": "maintenance.request", + "res_id": followup.id, + "view_mode": "form", + "target": "current", + } diff --git a/maintenance_equipment_usability/models/res_company.py b/maintenance_equipment_usability/models/res_company.py new file mode 100644 index 0000000..8fd4064 --- /dev/null +++ b/maintenance_equipment_usability/models/res_company.py @@ -0,0 +1,15 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + usability_grace_period_months = fields.Integer( + string="Usability Grace Period (Months)", + default=12, + help="Number of months after the latest completed maintenance result " + "during which the equipment stays usable.", + ) diff --git a/maintenance_equipment_usability/models/res_config_settings.py b/maintenance_equipment_usability/models/res_config_settings.py new file mode 100644 index 0000000..a578593 --- /dev/null +++ b/maintenance_equipment_usability/models/res_config_settings.py @@ -0,0 +1,16 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + usability_grace_period_months = fields.Integer( + related="company_id.usability_grace_period_months", + readonly=False, + string="Usability Grace Period (Months)", + help="Number of months after the latest completed maintenance result " + "during which the equipment stays usable.", + ) diff --git a/maintenance_equipment_usability/pyproject.toml b/maintenance_equipment_usability/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/maintenance_equipment_usability/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/maintenance_equipment_usability/readme/CONTRIBUTORS.md b/maintenance_equipment_usability/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..260160c --- /dev/null +++ b/maintenance_equipment_usability/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Quartile](https://www.quartile.co): + - Shinnosuke Morita diff --git a/maintenance_equipment_usability/readme/DESCRIPTION.md b/maintenance_equipment_usability/readme/DESCRIPTION.md new file mode 100644 index 0000000..d61125e --- /dev/null +++ b/maintenance_equipment_usability/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module adds an equipment status and a pass/fail maintenance result, and +computes equipment usability from the status, the latest completed maintenance +request result, and a configurable usability grace period. diff --git a/maintenance_equipment_usability/readme/USAGE.md b/maintenance_equipment_usability/readme/USAGE.md new file mode 100644 index 0000000..5d5f4c0 --- /dev/null +++ b/maintenance_equipment_usability/readme/USAGE.md @@ -0,0 +1,18 @@ +The equipment Status (Preparing, Operating, Idle, Disposal / Scrapped) gates +usability: only equipment in the Operating status can be usable. In any other +status the equipment is unusable regardless of its maintenance result. + +When a maintenance request is completed (it reaches a done stage), set its +Maintenance Result to Passed or Failed. For equipment in the Operating status, +usability is then computed from the latest completed request by completion date: + +- Failed result: the equipment is unusable. +- Passed result, within the grace period: the equipment is usable. +- Passed result, past the grace period: the equipment is unusable. +- No result yet: the equipment usability is unknown. + +The grace period runs from the latest completed maintenance result date and +defaults to 12 months. Configure it in Settings > Maintenance > Usability +Grace Period (for example 13 or 14 months). Usability is evaluated from the +current date, so equipment becomes unusable automatically once the grace +period elapses. diff --git a/maintenance_equipment_usability/static/description/index.html b/maintenance_equipment_usability/static/description/index.html new file mode 100644 index 0000000..a724765 --- /dev/null +++ b/maintenance_equipment_usability/static/description/index.html @@ -0,0 +1,446 @@ + + + + + + +Maintenance Equipment Usability + + + +
+

Maintenance Equipment Usability

+ + +

Beta License: LGPL-3 qrtl/rbkk-custom

+

This module adds an equipment status and a pass/fail maintenance result, +and computes equipment usability from the status, the latest completed +maintenance request result, and a configurable usability grace period.

+

Table of contents

+ +
+

Usage

+

The equipment Status (Preparing, Operating, Idle, Disposal / Scrapped) +gates usability: only equipment in the Operating status can be usable. +In any other status the equipment is unusable regardless of its +maintenance result.

+

When a maintenance request is completed (it reaches a done stage), set +its Maintenance Result to Passed or Failed. For equipment in the +Operating status, usability is then computed from the latest completed +request by completion date:

+
    +
  • Failed result: the equipment is unusable.
  • +
  • Passed result, within the grace period: the equipment is usable.
  • +
  • Passed result, past the grace period: the equipment is unusable.
  • +
  • No result yet: the equipment usability is unknown.
  • +
+

The grace period runs from the latest completed maintenance result date +and defaults to 12 months. Configure it in Settings > Maintenance > +Usability Grace Period (for example 13 or 14 months). Usability is +evaluated from the current date, so equipment becomes unusable +automatically once the grace period elapses.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Quartile
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

Current maintainer:

+

smorita7749

+

This module is part of the qrtl/rbkk-custom project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/maintenance_equipment_usability/tests/__init__.py b/maintenance_equipment_usability/tests/__init__.py new file mode 100644 index 0000000..a42d84c --- /dev/null +++ b/maintenance_equipment_usability/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import test_maintenance_equipment_usability diff --git a/maintenance_equipment_usability/tests/test_maintenance_equipment_usability.py b/maintenance_equipment_usability/tests/test_maintenance_equipment_usability.py new file mode 100644 index 0000000..33d2fae --- /dev/null +++ b/maintenance_equipment_usability/tests/test_maintenance_equipment_usability.py @@ -0,0 +1,135 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from dateutil.relativedelta import relativedelta + +from odoo import fields + +from odoo.addons.base.tests.common import BaseCommon + + +class TestMaintenanceEquipmentUsability(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.equipment = cls.env["maintenance.equipment"].create( + {"name": "Usability Test Equipment", "status": "operating"} + ) + cls.open_stage = cls.env.ref("maintenance.stage_0") + cls.done_stage = cls.env.ref("maintenance.stage_3") + cls.today = fields.Date.context_today(cls.equipment) + + def _create_request(self, stage, result="none", request_date=None, close_date=None): + vals = { + "name": f"Request {stage.name}", + "equipment_id": self.equipment.id, + "stage_id": stage.id, + "request_date": request_date or self.today, + "maintenance_result": result, + } + if close_date: + vals["close_date"] = close_date + return self.env["maintenance.request"].create(vals) + + def test_usability_unknown_without_completed_result(self): + self.assertEqual(self.equipment.usability_state, "unknown") + # A result on a request that is not done yet is ignored. + self._create_request(self.open_stage, result="passed") + self.assertEqual(self.equipment.usability_state, "unknown") + # A done request without a result is ignored. + self._create_request(self.done_stage, result="none", close_date=self.today) + self.assertEqual(self.equipment.usability_state, "unknown") + self.assertFalse(self.equipment.latest_maintenance_result_request_id) + self.assertFalse(self.equipment.latest_maintenance_result_date) + + def test_passed_within_grace_is_usable(self): + close = self.today - relativedelta(months=1) + self._create_request(self.done_stage, result="passed", close_date=close) + self.assertEqual(self.equipment.usability_state, "usable") + + def test_passed_past_grace_is_unusable(self): + close = self.today - relativedelta(months=13) + self._create_request(self.done_stage, result="passed", close_date=close) + self.assertEqual(self.equipment.usability_state, "unusable") + + def test_failed_is_unusable(self): + self._create_request(self.done_stage, result="failed", close_date=self.today) + self.assertEqual(self.equipment.usability_state, "unusable") + + def test_non_operating_status_is_unusable(self): + self._create_request( + self.done_stage, + result="passed", + close_date=self.today - relativedelta(months=1), + ) + # Operating + passed within grace = usable. + self.assertEqual(self.equipment.usability_state, "usable") + # Any non-operating status forces unusable regardless of the result. + self.equipment.status = "idle" + self.assertEqual(self.equipment.usability_state, "unusable") + self.equipment.status = "preparing" + self.assertEqual(self.equipment.usability_state, "unusable") + + def test_usability_uses_latest_result_by_close_date(self): + self._create_request( + self.done_stage, + result="passed", + close_date=self.today - relativedelta(months=1), + ) + self.assertEqual(self.equipment.usability_state, "usable") + failed = self._create_request( + self.done_stage, result="failed", close_date=self.today + ) + self.assertEqual(self.equipment.usability_state, "unusable") + self.assertEqual(self.equipment.latest_maintenance_result_request_id, failed) + + def test_grace_period_setting_extends_usability(self): + close = self.today - relativedelta(months=13) + self._create_request(self.done_stage, result="passed", close_date=close) + # Default 12-month grace: the 13-month-old result has expired. + self.assertEqual(self.equipment.usability_state, "unusable") + # Extending the grace period to 14 months makes it usable again. + self.env.company.usability_grace_period_months = 14 + self.equipment.invalidate_recordset(["usability_state"]) + self.assertEqual(self.equipment.usability_state, "usable") + + def test_search_usability_state(self): + usable_equipment = self.equipment + self._create_request( + self.done_stage, + result="passed", + close_date=self.today - relativedelta(months=1), + ) + expired_equipment = self.env["maintenance.equipment"].create( + {"name": "Expired Equipment", "status": "operating"} + ) + self.env["maintenance.request"].create( + { + "name": "Expired Request", + "equipment_id": expired_equipment.id, + "stage_id": self.done_stage.id, + "request_date": self.today - relativedelta(months=13), + "close_date": self.today - relativedelta(months=13), + "maintenance_result": "passed", + } + ) + usable = self.env["maintenance.equipment"].search( + [("usability_state", "=", "usable")] + ) + unusable = self.env["maintenance.equipment"].search( + [("usability_state", "=", "unusable")] + ) + self.assertIn(usable_equipment, usable) + self.assertNotIn(expired_equipment, usable) + self.assertIn(expired_equipment, unusable) + + def test_create_followup_request_from_failed(self): + request = self._create_request( + self.done_stage, result="failed", close_date=self.today + ) + request.action_create_followup_request() + followup = request.followup_request_ids + self.assertEqual(len(followup), 1) + self.assertEqual(followup.source_request_id, request) + self.assertEqual(followup.equipment_id, self.equipment) + self.assertEqual(followup.maintenance_type, "corrective") diff --git a/maintenance_equipment_usability/views/maintenance_equipment_views.xml b/maintenance_equipment_usability/views/maintenance_equipment_views.xml new file mode 100644 index 0000000..c38241a --- /dev/null +++ b/maintenance_equipment_usability/views/maintenance_equipment_views.xml @@ -0,0 +1,106 @@ + + + + + maintenance.equipment.form + maintenance.equipment + + + + + + + + + + + + + + + + + maintenance.equipment.list + maintenance.equipment + + + + + + + + + + maintenance.equipment.kanban + maintenance.equipment + + + + + + +
+ +
+
+ +
+
+ +
+
+
+
+ + + maintenance.equipment.search + maintenance.equipment + + + + + + + + + + + + + +
diff --git a/maintenance_equipment_usability/views/maintenance_request_views.xml b/maintenance_equipment_usability/views/maintenance_request_views.xml new file mode 100644 index 0000000..142a319 --- /dev/null +++ b/maintenance_equipment_usability/views/maintenance_request_views.xml @@ -0,0 +1,69 @@ + + + + + maintenance.request.form + maintenance.request + + + + + + + + + + + + maintenance.request.list + maintenance.request + + + + + + + + + + maintenance.request.search + maintenance.request + + + + + + + + + + + + + diff --git a/maintenance_equipment_usability/views/res_config_settings_views.xml b/maintenance_equipment_usability/views/res_config_settings_views.xml new file mode 100644 index 0000000..3b68b1e --- /dev/null +++ b/maintenance_equipment_usability/views/res_config_settings_views.xml @@ -0,0 +1,23 @@ + + + + + res.config.settings.form.maintenance.usability + res.config.settings + + + + + + + months + + + + + + From ae51bb179504f0019133793692f7ce87a8848b20 Mon Sep 17 00:00:00 2001 From: Morita Shinnosuke Date: Thu, 16 Jul 2026 01:04:28 +0000 Subject: [PATCH 2/4] change the logic of equipment validity --- maintenance_equipment_usability/README.rst | 19 ++++++++++++------- maintenance_equipment_usability/i18n/ja.po | 18 +++++++++++------- .../models/maintenance_equipment.py | 7 +++++-- .../models/maintenance_request.py | 1 + .../models/res_company.py | 8 +++++--- .../models/res_config_settings.py | 8 +++++--- .../readme/USAGE.md | 14 +++++++++----- .../static/description/index.html | 18 +++++++++++------- .../views/maintenance_request_views.xml | 1 + .../views/res_config_settings_views.xml | 4 ++-- 10 files changed, 62 insertions(+), 36 deletions(-) diff --git a/maintenance_equipment_usability/README.rst b/maintenance_equipment_usability/README.rst index 1e95b3a..f779c3f 100644 --- a/maintenance_equipment_usability/README.rst +++ b/maintenance_equipment_usability/README.rst @@ -7,7 +7,7 @@ Maintenance Equipment Usability !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:f595baf37a394c7841ac7376a7cf30d849fd9465b5e319b3b23d1d3d444c4a71 + !! source digest: sha256:55bf50b3bd1733e7339e1565bfc15fcf9a21f470fddf3addd825cd296b884f6f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -45,15 +45,20 @@ Operating status, usability is then computed from the latest completed request by completion date: - Failed result: the equipment is unusable. -- Passed result, within the grace period: the equipment is usable. -- Passed result, past the grace period: the equipment is unusable. +- Passed result, within the validity period: the equipment is usable. +- Passed result, past the validity period: the equipment is unusable. - No result yet: the equipment usability is unknown. -The grace period runs from the latest completed maintenance result date -and defaults to 12 months. Configure it in Settings > Maintenance > -Usability Grace Period (for example 13 or 14 months). Usability is +The validity period runs from the latest completed maintenance result +date and defaults to 12 months. Configure it in Settings > Maintenance > +Usability Validity Period (for example 13 or 14 months). Usability is evaluated from the current date, so equipment becomes unusable -automatically once the grace period elapses. +automatically once the validity period elapses. + +The validity always extends to the end of the target month. For example, +with a result passed on 15 June and a 13-month validity period, the +equipment stays usable until 31 July of the following year and becomes +unusable on 1 August. Bug Tracker =========== diff --git a/maintenance_equipment_usability/i18n/ja.po b/maintenance_equipment_usability/i18n/ja.po index dcf7af1..71f7faa 100644 --- a/maintenance_equipment_usability/i18n/ja.po +++ b/maintenance_equipment_usability/i18n/ja.po @@ -103,9 +103,13 @@ msgstr "結果なし" #: model:ir.model.fields,help:maintenance_equipment_usability.field_res_config_settings__usability_grace_period_months #: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.res_config_settings_view_form msgid "" -"Number of months after the latest completed maintenance result during which " -"the equipment stays usable." -msgstr "最新の完了した整備結果から、設備が使用可能であり続ける月数。" +"Number of months the equipment stays usable after its latest passed " +"maintenance result. Validity extends to the end of the target month (e.g. " +"passed on 15 June with 13 months stays usable until 31 July of the following " +"year)." +msgstr "" +"最後の合格結果から、設備が使用可能であり続ける月数。有効期限は対象月の月末まで" +"延長されます(例:6月15日に合格・13ヶ月なら翌年7月31日まで使用可能)。" #. module: maintenance_equipment_usability #: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__status__operating @@ -175,14 +179,14 @@ msgstr "使用可否" #. module: maintenance_equipment_usability #: model_terms:ir.ui.view,arch_db:maintenance_equipment_usability.res_config_settings_view_form -msgid "Usability Grace Period" -msgstr "猶予期間" +msgid "Usability Validity Period" +msgstr "使用可否の有効期間" #. module: maintenance_equipment_usability #: model:ir.model.fields,field_description:maintenance_equipment_usability.field_res_company__usability_grace_period_months #: model:ir.model.fields,field_description:maintenance_equipment_usability.field_res_config_settings__usability_grace_period_months -msgid "Usability Grace Period (Months)" -msgstr "猶予期間(月)" +msgid "Usability Validity Period (Months)" +msgstr "使用可否の有効期間(月)" #. module: maintenance_equipment_usability #: model:ir.model.fields.selection,name:maintenance_equipment_usability.selection__maintenance_equipment__usability_state__usable diff --git a/maintenance_equipment_usability/models/maintenance_equipment.py b/maintenance_equipment_usability/models/maintenance_equipment.py index 4a27704..e51bc3f 100644 --- a/maintenance_equipment_usability/models/maintenance_equipment.py +++ b/maintenance_equipment_usability/models/maintenance_equipment.py @@ -93,7 +93,8 @@ def _compute_usability(self): continue if request.maintenance_result == "failed": equipment.usability_state = "unusable" - elif today > result_date + relativedelta(months=grace_months): + elif today > result_date + relativedelta(months=grace_months, day=31): + # Validity extends to the end of the target month. equipment.usability_state = "unusable" else: equipment.usability_state = "usable" @@ -113,7 +114,9 @@ def _search_usability_state(self, operator, value): if not states: return [("id", "=", False)] result = "latest_maintenance_result_request_id.maintenance_result" - threshold = fields.Date.context_today(self) - relativedelta( + # Validity extends to the end of the target month, so the earliest + # still-valid result date is the first day of its month. + threshold = fields.Date.context_today(self).replace(day=1) - relativedelta( months=self.env.company.usability_grace_period_months ) domains = { diff --git a/maintenance_equipment_usability/models/maintenance_request.py b/maintenance_equipment_usability/models/maintenance_request.py index 1a69eae..526e739 100644 --- a/maintenance_equipment_usability/models/maintenance_request.py +++ b/maintenance_equipment_usability/models/maintenance_request.py @@ -36,6 +36,7 @@ def action_create_followup_request(self): { "name": _("Follow-up of %s", self.name), "equipment_id": self.equipment_id.id, + "maintenance_team_id": self.maintenance_team_id.id, "maintenance_type": "corrective", "source_request_id": self.id, } diff --git a/maintenance_equipment_usability/models/res_company.py b/maintenance_equipment_usability/models/res_company.py index 8fd4064..56822d8 100644 --- a/maintenance_equipment_usability/models/res_company.py +++ b/maintenance_equipment_usability/models/res_company.py @@ -8,8 +8,10 @@ class ResCompany(models.Model): _inherit = "res.company" usability_grace_period_months = fields.Integer( - string="Usability Grace Period (Months)", + string="Usability Validity Period (Months)", default=12, - help="Number of months after the latest completed maintenance result " - "during which the equipment stays usable.", + help="Number of months the equipment stays usable after its latest " + "passed maintenance result. Validity extends to the end of the target " + "month (e.g. passed on 15 June with 13 months stays usable until 31 " + "July of the following year).", ) diff --git a/maintenance_equipment_usability/models/res_config_settings.py b/maintenance_equipment_usability/models/res_config_settings.py index a578593..62faac3 100644 --- a/maintenance_equipment_usability/models/res_config_settings.py +++ b/maintenance_equipment_usability/models/res_config_settings.py @@ -10,7 +10,9 @@ class ResConfigSettings(models.TransientModel): usability_grace_period_months = fields.Integer( related="company_id.usability_grace_period_months", readonly=False, - string="Usability Grace Period (Months)", - help="Number of months after the latest completed maintenance result " - "during which the equipment stays usable.", + string="Usability Validity Period (Months)", + help="Number of months the equipment stays usable after its latest " + "passed maintenance result. Validity extends to the end of the target " + "month (e.g. passed on 15 June with 13 months stays usable until 31 " + "July of the following year).", ) diff --git a/maintenance_equipment_usability/readme/USAGE.md b/maintenance_equipment_usability/readme/USAGE.md index 5d5f4c0..7297028 100644 --- a/maintenance_equipment_usability/readme/USAGE.md +++ b/maintenance_equipment_usability/readme/USAGE.md @@ -7,12 +7,16 @@ Maintenance Result to Passed or Failed. For equipment in the Operating status, usability is then computed from the latest completed request by completion date: - Failed result: the equipment is unusable. -- Passed result, within the grace period: the equipment is usable. -- Passed result, past the grace period: the equipment is unusable. +- Passed result, within the validity period: the equipment is usable. +- Passed result, past the validity period: the equipment is unusable. - No result yet: the equipment usability is unknown. -The grace period runs from the latest completed maintenance result date and +The validity period runs from the latest completed maintenance result date and defaults to 12 months. Configure it in Settings > Maintenance > Usability -Grace Period (for example 13 or 14 months). Usability is evaluated from the -current date, so equipment becomes unusable automatically once the grace +Validity Period (for example 13 or 14 months). Usability is evaluated from the +current date, so equipment becomes unusable automatically once the validity period elapses. + +The validity always extends to the end of the target month. For example, with a +result passed on 15 June and a 13-month validity period, the equipment stays +usable until 31 July of the following year and becomes unusable on 1 August. diff --git a/maintenance_equipment_usability/static/description/index.html b/maintenance_equipment_usability/static/description/index.html index a724765..c20feca 100644 --- a/maintenance_equipment_usability/static/description/index.html +++ b/maintenance_equipment_usability/static/description/index.html @@ -367,7 +367,7 @@

Maintenance Equipment Usability

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:f595baf37a394c7841ac7376a7cf30d849fd9465b5e319b3b23d1d3d444c4a71 +!! source digest: sha256:55bf50b3bd1733e7339e1565bfc15fcf9a21f470fddf3addd825cd296b884f6f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 qrtl/rbkk-custom

This module adds an equipment status and a pass/fail maintenance result, @@ -398,15 +398,19 @@

Usage

request by completion date:

  • Failed result: the equipment is unusable.
  • -
  • Passed result, within the grace period: the equipment is usable.
  • -
  • Passed result, past the grace period: the equipment is unusable.
  • +
  • Passed result, within the validity period: the equipment is usable.
  • +
  • Passed result, past the validity period: the equipment is unusable.
  • No result yet: the equipment usability is unknown.
-

The grace period runs from the latest completed maintenance result date -and defaults to 12 months. Configure it in Settings > Maintenance > -Usability Grace Period (for example 13 or 14 months). Usability is +

The validity period runs from the latest completed maintenance result +date and defaults to 12 months. Configure it in Settings > Maintenance > +Usability Validity Period (for example 13 or 14 months). Usability is evaluated from the current date, so equipment becomes unusable -automatically once the grace period elapses.

+automatically once the validity period elapses.

+

The validity always extends to the end of the target month. For example, +with a result passed on 15 June and a 13-month validity period, the +equipment stays usable until 31 July of the following year and becomes +unusable on 1 August.

Bug Tracker

diff --git a/maintenance_equipment_usability/views/maintenance_request_views.xml b/maintenance_equipment_usability/views/maintenance_request_views.xml index 142a319..7420b16 100644 --- a/maintenance_equipment_usability/views/maintenance_request_views.xml +++ b/maintenance_equipment_usability/views/maintenance_request_views.xml @@ -9,6 +9,7 @@ +