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
+
+
+
+
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.
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.
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.
+
+
+
+
+
+ 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
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
This module adds an equipment status and a pass/fail maintenance result,
@@ -398,15 +398,19 @@
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.