Files
maintenance-tools/maintenance_service_http_monitoring/models/maintenance_equipment.py
Stéphan Sainléger c238e54808 [IMP] maintenance_service_http_monitoring: rework maintenance.request creation
Previously, a single ``maintenance.request`` was created per equipment,
regardless of how many services were down on that equipment. The name
was ``[HTTP KO] {equipment.name}`` and deduplication relied on a
name+date+equipment search that was fragile (manual clear of the field
would lose the reference to an existing open request).

This commit reworks the whole creation logic:

- **1 request per KO service** instead of 1 per equipment. Each failing
  ``service.instance`` gets its own ``maintenance.request``, allowing
  fine-grained tracking and independent resolution.

- **Request name** is now ``[HTTP KO] {service_url}``, making it
  immediately identifiable without opening the record.

- **Description** includes the error detail: ``HTTP {status_code}`` for
  HTTP errors, or a human-readable network error label when
  ``last_http_status_code == -1`` (timeout / DNS / SSL failures).

- **Deduplication** is now based solely on whether an open (non-done)
  ``maintenance.request`` already exists on the ``service.instance``
  via the new ``http_maintenance_request`` field. No date boundary —
  as long as the request is open, no new one is created.

- **``http_maintenance_request``** field moved from
  ``maintenance.equipment`` to ``service.instance``, where it belongs
  given the 1-request-per-service model. It is exposed as an optional
  hidden column in the service instance list view.

- ``_build_ko_services_description()`` is removed (no longer needed).

- ``create_http_maintenance_request()`` now receives a single
  ``service.instance`` recordset instead of a list.

Tests updated accordingly (14 tests total):
- Tests 2, 4, 9, 10, 12, 13 now assert on
  ``service_instance.http_maintenance_request``.
- Test 2 also verifies the request name contains the service URL and
  the description contains the HTTP status code.
- New test 14 asserts that two KO services on the same equipment
  produce two distinct requests with the correct names.
2026-06-15 17:47:25 +02:00

153 lines
4.9 KiB
Python

import logging
from datetime import timedelta
from odoo import api, fields, models
try:
import requests as http_requests
except ImportError:
http_requests = None
_logger = logging.getLogger(__name__)
WEBHOOK_TIMEOUT = 10 # seconds
class MaintenanceEquipment(models.Model):
_inherit = "maintenance.equipment"
maintenance_mode = fields.Boolean(
default=False,
tracking=True,
)
maintenance_mode_start = fields.Datetime(
readonly=True,
)
maintenance_mode_end = fields.Datetime(
readonly=True,
help="Computed from start + configured duration",
)
def action_activate_maintenance_mode(self):
for rec in self:
duration = int(
self.env["ir.config_parameter"]
.sudo()
.get_param(
"maintenance_service_http_monitoring.maintenance_mode_duration", 4
)
)
now = fields.Datetime.now()
rec.write(
{
"maintenance_mode": True,
"maintenance_mode_start": now,
"maintenance_mode_end": now + timedelta(hours=duration),
}
)
def action_deactivate_maintenance_mode(self):
for rec in self:
rec.write(
{
"maintenance_mode": False,
"maintenance_mode_start": False,
"maintenance_mode_end": False,
}
)
@api.model
def cron_deactivate_expired_maintenance_mode(self):
now = fields.Datetime.now()
expired = self.search(
[
("maintenance_mode", "=", True),
("maintenance_mode_end", "<=", now),
]
)
expired.action_deactivate_maintenance_mode()
def create_http_maintenance_request(self, ko_service):
"""
Create or return the open maintenance.request for a single KO service.
Deduplication: if ko_service already has an open (non-done) request,
return it without creating a new one.
"""
self.ensure_one()
existing = ko_service.http_maintenance_request
if existing and not existing.stage_id.done:
return existing
status_code = ko_service.last_http_status_code
if status_code == -1:
error_detail = "Erreur réseau (timeout / DNS / SSL)"
else:
error_detail = f"HTTP {status_code}"
name = f"[HTTP KO] {ko_service.service_url}"
description = f"Service KO: {ko_service.service_url}\n{error_detail}"
vals = {
"name": name,
"equipment_id": self.id,
"priority": "2",
"maintenance_type": "corrective",
"description": description,
}
if self.employee_id:
vals["employee_id"] = self.employee_id.id
if self.technician_user_id:
vals["user_id"] = self.technician_user_id.id
if self.maintenance_team_id:
vals["maintenance_team_id"] = self.maintenance_team_id.id
else:
team = self.env["maintenance.team"].search([], limit=1)
if team:
vals["maintenance_team_id"] = team.id
request = self.env["maintenance.request"].create(vals)
ko_service.http_maintenance_request = request.id
self._notify_webhook(request, ko_service)
return request
def _notify_webhook(self, request, ko_service):
"""
Send a webhook notification when a new maintenance request is created.
"""
ICP = self.env["ir.config_parameter"].sudo()
webhook_url = ICP.get_param(
"maintenance_service_http_monitoring.webhook_url", ""
)
if not webhook_url:
return
webhook_user = ICP.get_param(
"maintenance_service_http_monitoring.webhook_user", ""
)
webhook_password = ICP.get_param(
"maintenance_service_http_monitoring.webhook_password", ""
)
base_url = ICP.get_param("web.base.url", "")
link = (
f"{base_url}/web#id={request.id}&model=maintenance.request&view_type=form"
)
payload = {
"id": request.id,
"name": request.name,
"description": request.description or "",
"equipment": self.name,
"link": link,
}
auth = None
if webhook_user and webhook_password:
auth = (webhook_user, webhook_password)
try:
http_requests.post(
webhook_url,
json=payload,
auth=auth,
timeout=WEBHOOK_TIMEOUT,
)
except Exception as e:
_logger.warning(
"Webhook notification failed for maintenance request %s: %s",
request.id,
e,
)