diff --git a/maintenance_additional_attributes/README.rst b/maintenance_additional_attributes/README.rst new file mode 100644 index 0000000..6936a77 --- /dev/null +++ b/maintenance_additional_attributes/README.rst @@ -0,0 +1,78 @@ +================================= +Maintenance Additional Attributes +================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:37bbe9b661964d0987254eb7d6c63f466c5c4d4ddfba20425590e2bdd3d5b603 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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_additional_attributes + :alt: qrtl/rbkk-custom + +|badge1| |badge2| |badge3| + +This module adds attributes to maintenance equipment and maintenance +requests. + +It also allows to set an alert period on a maintenance request. A daily +scheduled action creates a *Maintenance Alert* activity for the +technician (or the owner if no technician is assigned) once the alert +period before the scheduled date is reached. The alert is dropped and +sent again when the scheduled date or the alert period is changed, and +it is marked as done when the request is completed. + +**Table of contents** + +.. contents:: + :local: + +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_additional_attributes/__init__.py b/maintenance_additional_attributes/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/maintenance_additional_attributes/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/maintenance_additional_attributes/__manifest__.py b/maintenance_additional_attributes/__manifest__.py new file mode 100644 index 0000000..e1c3d48 --- /dev/null +++ b/maintenance_additional_attributes/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +{ + "name": "Maintenance Additional Attributes", + "version": "18.0.1.0.0", + "category": "Maintenance", + "summary": "Add specific attributes to maintenance equipment and requests", + "author": "Quartile", + "website": "https://www.quartile.co", + "maintainers": ["smorita7749"], + "license": "LGPL-3", + "depends": ["maintenance", "purchase", "stock"], + "data": [ + "security/ir.model.access.csv", + "data/mail_activity_type.xml", + "data/ir_cron.xml", + "views/maintenance_equipment_department_views.xml", + "views/maintenance_equipment_generic_name_views.xml", + "views/maintenance_equipment_views.xml", + "views/maintenance_request_views.xml", + ], + "installable": True, +} diff --git a/maintenance_additional_attributes/data/ir_cron.xml b/maintenance_additional_attributes/data/ir_cron.xml new file mode 100644 index 0000000..04264ba --- /dev/null +++ b/maintenance_additional_attributes/data/ir_cron.xml @@ -0,0 +1,12 @@ + + + + Maintenance: Send Maintenance Request Alerts + + code + model._cron_send_maintenance_alerts() + + 1 + days + + diff --git a/maintenance_additional_attributes/data/mail_activity_type.xml b/maintenance_additional_attributes/data/mail_activity_type.xml new file mode 100644 index 0000000..4b4eee2 --- /dev/null +++ b/maintenance_additional_attributes/data/mail_activity_type.xml @@ -0,0 +1,8 @@ + + + + Maintenance Alert + fa-bell + maintenance.request + + diff --git a/maintenance_additional_attributes/i18n/ja.po b/maintenance_additional_attributes/i18n/ja.po new file mode 100644 index 0000000..2290135 --- /dev/null +++ b/maintenance_additional_attributes/i18n/ja.po @@ -0,0 +1,249 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * maintenance_additional_attributes +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-07-24 07:57+0000\n" +"PO-Revision-Date: 2026-07-03 03:10+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__acquisition_date +msgid "Acquisition Date" +msgstr "取得年月日" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__active +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__active +msgid "Active" +msgstr "有効" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_request__alert_period +msgid "Alert Period" +msgstr "アラート期間" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_request__alert_period_unit +msgid "Alert Period Unit" +msgstr "アラート期間の単位" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_request__alert_sent +msgid "Alert Sent" +msgstr "アラート送信済み" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,help:maintenance_additional_attributes.field_maintenance_request__alert_period +msgid "" +"Amount of time before the scheduled date at which the responsible user " +"should be alerted about this maintenance." +msgstr "予定日より前に担当者へアラートを通知する期間。" + +#. module: maintenance_additional_attributes +#: model_terms:ir.ui.view,arch_db:maintenance_additional_attributes.maintenance_equipment_department_view_search +#: model_terms:ir.ui.view,arch_db:maintenance_additional_attributes.maintenance_equipment_generic_name_view_search +msgid "Archived" +msgstr "アーカイブ済" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__create_uid +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__create_uid +msgid "Created by" +msgstr "作成者" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__create_date +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__create_date +msgid "Created on" +msgstr "作成日" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields.selection,name:maintenance_additional_attributes.selection__maintenance_request__alert_period_unit__day +msgid "Days" +msgstr "日" + +#. module: maintenance_additional_attributes +#: model:ir.actions.act_window,name:maintenance_additional_attributes.maintenance_equipment_department_action +#: model:ir.ui.menu,name:maintenance_additional_attributes.menu_maintenance_equipment_department +msgid "Departments" +msgstr "部門" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__display_name +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__display_name +msgid "Display Name" +msgstr "表示名" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__equipment_department_id +msgid "Equipment Department" +msgstr "設備部門" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__is_fixed_asset +msgid "Fixed Asset" +msgstr "固定資産" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__fixed_asset_code +msgid "Fixed Asset Code" +msgstr "固定資産コード" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__is_gmp +msgid "GMP" +msgstr "GMP" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__generic_name_id +msgid "General Name" +msgstr "一般名称" + +#. module: maintenance_additional_attributes +#: model:ir.actions.act_window,name:maintenance_additional_attributes.maintenance_equipment_generic_name_action +#: model:ir.ui.menu,name:maintenance_additional_attributes.menu_maintenance_equipment_generic_name +msgid "General Names" +msgstr "一般名称" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__id +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__id +msgid "ID" +msgstr "ID" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__write_uid +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__write_uid +msgid "Last Updated by" +msgstr "最終更新者" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__write_date +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__write_date +msgid "Last Updated on" +msgstr "最終更新日" + +#. module: maintenance_additional_attributes +#: model:mail.activity.type,name:maintenance_additional_attributes.mail_act_maintenance_alert +msgid "Maintenance Alert" +msgstr "メンテナンスアラート" + +#. module: maintenance_additional_attributes +#: model:ir.model,name:maintenance_additional_attributes.model_maintenance_equipment +msgid "Maintenance Equipment" +msgstr "整備設備" + +#. module: maintenance_additional_attributes +#: model:ir.model,name:maintenance_additional_attributes.model_maintenance_equipment_department +msgid "Maintenance Equipment Department" +msgstr "整備設備部門" + +#. module: maintenance_additional_attributes +#: model:ir.model,name:maintenance_additional_attributes.model_maintenance_equipment_generic_name +msgid "Maintenance Equipment General Name" +msgstr "整備設備一般名称" + +#. module: maintenance_additional_attributes +#: model:ir.model,name:maintenance_additional_attributes.model_maintenance_request +msgid "Maintenance Request" +msgstr "整備依頼" + +#. module: maintenance_additional_attributes +#: model:ir.cron,name:maintenance_additional_attributes.maintenance_request_alert_cron +msgid "Maintenance: Send Maintenance Request Alerts" +msgstr "メンテナンス: 保守依頼のアラート送信" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__management_no +msgid "Management No" +msgstr "管理番号" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__manufacturer +msgid "Manufacturer" +msgstr "メーカー" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__manufacturer_serial_no +msgid "Manufacturer Serial No" +msgstr "シリアル番号" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__is_measuring_instrument +msgid "Measuring Instrument" +msgstr "測定器" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields.selection,name:maintenance_additional_attributes.selection__maintenance_request__alert_period_unit__month +msgid "Months" +msgstr "月" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__name +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__name +msgid "Name" +msgstr "名称" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__is_national_project +msgid "National Project" +msgstr "国家プロジェクト" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__national_project_name +msgid "National Project Name" +msgstr "国家プロジェクト名" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__purchase_order_id +msgid "Purchase Order" +msgstr "購買オーダ" + +#. module: maintenance_additional_attributes +#: code:addons/maintenance_additional_attributes/models/maintenance_request.py:0 +msgid "Scheduled Maintenance" +msgstr "メンテナンス予定" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__set_name +msgid "Set Name" +msgstr "セット名称" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__smart_search +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_department__smart_search +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment_generic_name__smart_search +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_request__smart_search +msgid "Smart Search" +msgstr "スマート検索" + +#. module: maintenance_additional_attributes +#: model:ir.model.constraint,message:maintenance_additional_attributes.constraint_maintenance_equipment_department_name_uniq +#: model:ir.model.constraint,message:maintenance_additional_attributes.constraint_maintenance_equipment_generic_name_name_uniq +msgid "The name must be unique." +msgstr "名称は一意である必要があります。" + +#. module: maintenance_additional_attributes +#: code:addons/maintenance_additional_attributes/models/maintenance_request.py:0 +msgid "The scheduled maintenance date (%s) is approaching." +msgstr "メンテナンス予定日(%s)が近づいています。" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields,field_description:maintenance_additional_attributes.field_maintenance_equipment__location_id +msgid "Used in location" +msgstr "使用場所" + +#. module: maintenance_additional_attributes +#: model:ir.model.fields.selection,name:maintenance_additional_attributes.selection__maintenance_request__alert_period_unit__week +msgid "Weeks" +msgstr "週" diff --git a/maintenance_additional_attributes/models/__init__.py b/maintenance_additional_attributes/models/__init__.py new file mode 100644 index 0000000..70b2e46 --- /dev/null +++ b/maintenance_additional_attributes/models/__init__.py @@ -0,0 +1,4 @@ +from . import maintenance_equipment_department +from . import maintenance_equipment_generic_name +from . import maintenance_equipment +from . import maintenance_request diff --git a/maintenance_additional_attributes/models/maintenance_equipment.py b/maintenance_additional_attributes/models/maintenance_equipment.py new file mode 100644 index 0000000..e1b3795 --- /dev/null +++ b/maintenance_additional_attributes/models/maintenance_equipment.py @@ -0,0 +1,36 @@ +# 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 MaintenanceEquipment(models.Model): + _name = "maintenance.equipment" + _inherit = ["maintenance.equipment", "image.mixin"] + + set_name = fields.Char() + generic_name_id = fields.Many2one( + comodel_name="maintenance.equipment.generic.name", + string="General Name", + ) + is_fixed_asset = fields.Boolean(string="Fixed Asset") + fixed_asset_code = fields.Char() + is_gmp = fields.Boolean(string="GMP") + management_no = fields.Char() + is_measuring_instrument = fields.Boolean(string="Measuring Instrument") + is_national_project = fields.Boolean(string="National Project") + national_project_name = fields.Char() + location_id = fields.Many2one( + comodel_name="stock.location", + string="Used in location", + domain="[('usage', '=', 'view')]", + tracking=True, + ) + manufacturer = fields.Char() + manufacturer_serial_no = fields.Char() + purchase_order_id = fields.Many2one(comodel_name="purchase.order") + acquisition_date = fields.Date() + equipment_department_id = fields.Many2one( + comodel_name="maintenance.equipment.department", + string="Equipment Department", + ) diff --git a/maintenance_additional_attributes/models/maintenance_equipment_department.py b/maintenance_additional_attributes/models/maintenance_equipment_department.py new file mode 100644 index 0000000..ea26e5c --- /dev/null +++ b/maintenance_additional_attributes/models/maintenance_equipment_department.py @@ -0,0 +1,17 @@ +# 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 MaintenanceEquipmentDepartment(models.Model): + _name = "maintenance.equipment.department" + _description = "Maintenance Equipment Department" + _order = "name" + + name = fields.Char(required=True) + active = fields.Boolean(default=True) + + _sql_constraints = [ + ("name_uniq", "unique (name)", "The name must be unique."), + ] diff --git a/maintenance_additional_attributes/models/maintenance_equipment_generic_name.py b/maintenance_additional_attributes/models/maintenance_equipment_generic_name.py new file mode 100644 index 0000000..91a6a84 --- /dev/null +++ b/maintenance_additional_attributes/models/maintenance_equipment_generic_name.py @@ -0,0 +1,17 @@ +# 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 MaintenanceEquipmentGenericName(models.Model): + _name = "maintenance.equipment.generic.name" + _description = "Maintenance Equipment General Name" + _order = "name" + + name = fields.Char(required=True) + active = fields.Boolean(default=True) + + _sql_constraints = [ + ("name_uniq", "unique (name)", "The name must be unique."), + ] diff --git a/maintenance_additional_attributes/models/maintenance_request.py b/maintenance_additional_attributes/models/maintenance_request.py new file mode 100644 index 0000000..2033ce8 --- /dev/null +++ b/maintenance_additional_attributes/models/maintenance_request.py @@ -0,0 +1,106 @@ +# 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 +from odoo.tools.misc import format_datetime + + +class MaintenanceRequest(models.Model): + _inherit = "maintenance.request" + + alert_period = fields.Integer( + help="Amount of time before the scheduled date at which the " + "responsible user should be alerted about this maintenance.", + ) + alert_period_unit = fields.Selection( + selection=[ + ("day", "Days"), + ("week", "Weeks"), + ("month", "Months"), + ], + default="month", + ) + alert_sent = fields.Boolean(copy=False) + + @api.model_create_multi + def create(self, vals_list): + # The standard create() and write() derive the close date from the + # stage, discarding the date the user recorded (see write()). Return the + # requests in the original environment so that the context does not leak + # to the caller. + return ( + super( + MaintenanceRequest, self.with_context(maintenance_keep_close_date=True) + ) + .create(vals_list) + .with_env(self.env) + ) + + def write(self, vals): + # The standard create() and write() rewrite the close date whenever the + # stage changes (today() when the request is completed, empty + # otherwise), which discards the date the user recorded. Those updates + # come back here as a write on the close date alone, so ignore them, + # except for the requests that have no date recorded yet. + if list(vals) == ["close_date"] and self.env.context.get( + "maintenance_keep_close_date" + ): + requests = self.filtered(lambda request: not request.close_date) + return super(MaintenanceRequest, requests).write(vals) if requests else True + # Drop the outdated alert and let it be sent again on the new timing. + if any( + field in vals + for field in ("schedule_date", "alert_period", "alert_period_unit") + ): + vals["alert_sent"] = False + self.activity_unlink( + ["maintenance_additional_attributes.mail_act_maintenance_alert"] + ) + res = super( + MaintenanceRequest, self.with_context(maintenance_keep_close_date=True) + ).write(vals) + if "stage_id" in vals: + # Mark the alert as handled once the request is completed. + self.filtered("done").activity_feedback( + ["maintenance_additional_attributes.mail_act_maintenance_alert"] + ) + return res + + def _cron_send_maintenance_alerts(self): + requests = self.search( + [ + ("archive", "=", False), + ("stage_id.done", "=", False), + ("schedule_date", "!=", False), + ("alert_period", ">", 0), + ("alert_period_unit", "!=", False), + ("alert_sent", "=", False), + ] + ) + now = fields.Datetime.now() + for request in requests: + alert_date = request.schedule_date - relativedelta( + **{f"{request.alert_period_unit}s": request.alert_period} + ) + if now < alert_date: + continue + user = request.user_id or request.equipment_id.technician_user_id + if not user: + continue + env = self.env(context={**self.env.context, "lang": user.lang}) + activity = request.activity_schedule( + "maintenance_additional_attributes.mail_act_maintenance_alert", + date_deadline=fields.Datetime.context_timestamp( + request.with_context(tz=user.tz), alert_date + ).date(), + summary=env._("Scheduled Maintenance"), + note=env._( + "The scheduled maintenance date (%s) is approaching.", + format_datetime(env, request.schedule_date, tz=user.tz), + ), + user_id=user.id, + ) + if activity: + request.alert_sent = True diff --git a/maintenance_additional_attributes/pyproject.toml b/maintenance_additional_attributes/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/maintenance_additional_attributes/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/maintenance_additional_attributes/readme/CONTRIBUTORS.md b/maintenance_additional_attributes/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..260160c --- /dev/null +++ b/maintenance_additional_attributes/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Quartile](https://www.quartile.co): + - Shinnosuke Morita diff --git a/maintenance_additional_attributes/readme/DESCRIPTION.md b/maintenance_additional_attributes/readme/DESCRIPTION.md new file mode 100644 index 0000000..f1e165a --- /dev/null +++ b/maintenance_additional_attributes/readme/DESCRIPTION.md @@ -0,0 +1,8 @@ +This module adds attributes to maintenance equipment and maintenance requests. + +It also allows to set an alert period on a maintenance request. A daily +scheduled action creates a *Maintenance Alert* activity for the technician (or +the owner if no technician is assigned) once the alert period before the +scheduled date is reached. The alert is dropped and sent again when the +scheduled date or the alert period is changed, and it is marked as done when the +request is completed. diff --git a/maintenance_additional_attributes/security/ir.model.access.csv b/maintenance_additional_attributes/security/ir.model.access.csv new file mode 100644 index 0000000..56c80d5 --- /dev/null +++ b/maintenance_additional_attributes/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_maintenance_equipment_generic_name_user,maintenance.equipment.generic.name user,model_maintenance_equipment_generic_name,base.group_user,1,0,0,0 +access_maintenance_equipment_generic_name_manager,maintenance.equipment.generic.name manager,model_maintenance_equipment_generic_name,maintenance.group_equipment_manager,1,1,1,1 +access_maintenance_equipment_department_user,maintenance.equipment.department user,model_maintenance_equipment_department,base.group_user,1,0,0,0 +access_maintenance_equipment_department_manager,maintenance.equipment.department manager,model_maintenance_equipment_department,maintenance.group_equipment_manager,1,1,1,1 diff --git a/maintenance_additional_attributes/static/description/index.html b/maintenance_additional_attributes/static/description/index.html new file mode 100644 index 0000000..71f80b7 --- /dev/null +++ b/maintenance_additional_attributes/static/description/index.html @@ -0,0 +1,427 @@ + + + + + +Maintenance Additional Attributes + + + +
+

Maintenance Additional Attributes

+ + +

Beta License: LGPL-3 qrtl/rbkk-custom

+

This module adds attributes to maintenance equipment and maintenance +requests.

+

It also allows to set an alert period on a maintenance request. A daily +scheduled action creates a Maintenance Alert activity for the +technician (or the owner if no technician is assigned) once the alert +period before the scheduled date is reached. The alert is dropped and +sent again when the scheduled date or the alert period is changed, and +it is marked as done when the request is completed.

+

Table of contents

+ +
+

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_additional_attributes/tests/__init__.py b/maintenance_additional_attributes/tests/__init__.py new file mode 100644 index 0000000..eb24e75 --- /dev/null +++ b/maintenance_additional_attributes/tests/__init__.py @@ -0,0 +1 @@ +from . import test_maintenance_request diff --git a/maintenance_additional_attributes/tests/test_maintenance_request.py b/maintenance_additional_attributes/tests/test_maintenance_request.py new file mode 100644 index 0000000..4631f9e --- /dev/null +++ b/maintenance_additional_attributes/tests/test_maintenance_request.py @@ -0,0 +1,117 @@ +# 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.tests.common import TransactionCase + + +class TestMaintenanceRequest(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stage_open = cls.env["maintenance.stage"].create( + {"name": "Test Open", "sequence": 100} + ) + cls.stage_done = cls.env["maintenance.stage"].create( + {"name": "Test Done", "sequence": 110, "done": True} + ) + cls.team = cls.env["maintenance.team"].create({"name": "Test Team"}) + cls.technician = cls.env["res.users"].create( + {"name": "Test Technician", "login": "test_technician"} + ) + cls.activity_type = cls.env.ref( + "maintenance_additional_attributes.mail_act_maintenance_alert" + ) + cls.close_date = fields.Date.to_date("2026-07-20") + + def _create_request(self, **vals): + """Create a request that is due for an alert, unless overridden.""" + return self.env["maintenance.request"].create( + { + "name": "Test Request", + "maintenance_team_id": self.team.id, + "stage_id": self.stage_open.id, + "user_id": self.technician.id, + "schedule_date": fields.Datetime.now() + relativedelta(days=10), + "alert_period": 1, + "alert_period_unit": "month", + **vals, + } + ) + + def _run_cron(self, request): + self.env["maintenance.request"]._cron_send_maintenance_alerts() + return request.activity_ids.filtered( + lambda activity: activity.activity_type_id == self.activity_type + ) + + def test_close_date_kept_before_completion(self): + """The close date can be recorded while the request is still open.""" + self.assertEqual( + self._create_request(close_date=self.close_date).close_date, + self.close_date, + ) + + def test_close_date_kept_on_completion(self): + """Completing the request must not overwrite the recorded date.""" + request = self._create_request(close_date=self.close_date) + request.stage_id = self.stage_done + self.assertEqual(request.close_date, self.close_date) + + def test_close_date_can_be_updated(self): + """The recorded date must remain editable after the request is created.""" + request = self._create_request(close_date=self.close_date) + request.close_date = self.close_date + relativedelta(days=1) + self.assertEqual(request.close_date, self.close_date + relativedelta(days=1)) + + def test_close_date_defaults_to_today_on_completion(self): + """Without a recorded date, the standard behavior still applies.""" + request = self._create_request() + request.stage_id = self.stage_done + self.assertEqual(request.close_date, fields.Date.today()) + + def test_alert_sent_once_within_period(self): + request = self._create_request() + self.assertTrue(self._run_cron(request)) + self.assertTrue(request.alert_sent) + self.assertEqual(len(self._run_cron(request)), 1) + + def test_alert_not_sent_outside_period(self): + request = self._create_request( + schedule_date=fields.Datetime.now() + relativedelta(days=60) + ) + self.assertFalse(self._run_cron(request)) + self.assertFalse(request.alert_sent) + + def test_alert_reset_on_schedule_date_change(self): + request = self._create_request() + self._run_cron(request) + request.schedule_date = fields.Datetime.now() + relativedelta(days=60) + self.assertFalse(request.alert_sent) + self.assertFalse( + request.activity_ids.filtered( + lambda activity: activity.activity_type_id == self.activity_type + ) + ) + + def test_alert_assigned_to_technician(self): + self.assertEqual( + self._run_cron(self._create_request()).user_id, self.technician + ) + + def test_alert_falls_back_to_equipment_technician(self): + """A technician assigned to the equipment after the request was + created is not propagated by the standard compute.""" + equipment = self.env["maintenance.equipment"].create({"name": "Test Equipment"}) + request = self._create_request(equipment_id=equipment.id, user_id=False) + equipment.technician_user_id = self.technician + self.assertFalse(request.user_id) + self.assertEqual(self._run_cron(request).user_id, self.technician) + + def test_alert_not_sent_without_technician(self): + """The alert waits until a technician is assigned.""" + request = self._create_request(user_id=False) + self.assertFalse(self._run_cron(request)) + self.assertFalse(request.alert_sent) diff --git a/maintenance_additional_attributes/views/maintenance_equipment_department_views.xml b/maintenance_additional_attributes/views/maintenance_equipment_department_views.xml new file mode 100644 index 0000000..03db46c --- /dev/null +++ b/maintenance_additional_attributes/views/maintenance_equipment_department_views.xml @@ -0,0 +1,39 @@ + + + + maintenance.equipment.department.list + maintenance.equipment.department + + + + + + + + + maintenance.equipment.department.search + maintenance.equipment.department + + + + + + + + + Departments + maintenance.equipment.department + list + + + diff --git a/maintenance_additional_attributes/views/maintenance_equipment_generic_name_views.xml b/maintenance_additional_attributes/views/maintenance_equipment_generic_name_views.xml new file mode 100644 index 0000000..41386a0 --- /dev/null +++ b/maintenance_additional_attributes/views/maintenance_equipment_generic_name_views.xml @@ -0,0 +1,42 @@ + + + + maintenance.equipment.generic.name.list + maintenance.equipment.generic.name + + + + + + + + + maintenance.equipment.generic.name.search + maintenance.equipment.generic.name + + + + + + + + + General Names + maintenance.equipment.generic.name + list + + + diff --git a/maintenance_additional_attributes/views/maintenance_equipment_views.xml b/maintenance_additional_attributes/views/maintenance_equipment_views.xml new file mode 100644 index 0000000..a3a3898 --- /dev/null +++ b/maintenance_additional_attributes/views/maintenance_equipment_views.xml @@ -0,0 +1,120 @@ + + + + maintenance.equipment.form + maintenance.equipment + + +
+ +
+ + + + + + + 1 + + + + + + + + + + + + + + + + + + + + +
+ + maintenance.equipment.list + maintenance.equipment + + + + + + + + + + + + + + + + + + + + + + maintenance.equipment.search + maintenance.equipment + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/maintenance_additional_attributes/views/maintenance_request_views.xml b/maintenance_additional_attributes/views/maintenance_request_views.xml new file mode 100644 index 0000000..2fea8c2 --- /dev/null +++ b/maintenance_additional_attributes/views/maintenance_request_views.xml @@ -0,0 +1,21 @@ + + + + maintenance.request.form.additional.attributes + maintenance.request + + + + 0 + 0 + + + + + +