mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-20 14:52:09 +02:00
Initial commit: OCA Technical packages (595 packages)
This commit is contained in:
commit
2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions
|
|
@ -0,0 +1,4 @@
|
|||
from . import maintenance_kind
|
||||
from . import maintenance_plan
|
||||
from . import maintenance_equipment
|
||||
from . import maintenance_request
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# Copyright 2019-20 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class MaintenanceEquipment(models.Model):
|
||||
|
||||
_inherit = "maintenance.equipment"
|
||||
|
||||
maintenance_plan_ids = fields.One2many(
|
||||
string="Maintenance plan",
|
||||
comodel_name="maintenance.plan",
|
||||
inverse_name="equipment_id",
|
||||
)
|
||||
maintenance_plan_count = fields.Integer(
|
||||
compute="_compute_maintenance_plan_count",
|
||||
store=True,
|
||||
)
|
||||
search_maintenance_plan_count = fields.Integer(
|
||||
compute="_compute_search_maintenance_plan_count",
|
||||
string="Maintenance All Plan Count",
|
||||
)
|
||||
maintenance_team_required = fields.Boolean(compute="_compute_team_required")
|
||||
notes = fields.Text()
|
||||
|
||||
@api.depends("maintenance_plan_ids", "maintenance_plan_ids.active")
|
||||
def _compute_maintenance_plan_count(self):
|
||||
for equipment in self:
|
||||
equipment.maintenance_plan_count = len(
|
||||
equipment.with_context(active_test=False).maintenance_plan_ids
|
||||
)
|
||||
|
||||
@api.depends("maintenance_plan_ids")
|
||||
def _compute_search_maintenance_plan_count(self):
|
||||
for equipment in self:
|
||||
equipment.search_maintenance_plan_count = (
|
||||
self.env["maintenance.plan"]
|
||||
.with_context(active_test=False)
|
||||
.search_count([("search_equipment_id", "=", equipment.id)])
|
||||
)
|
||||
|
||||
@api.depends("maintenance_plan_ids")
|
||||
def _compute_team_required(self):
|
||||
for equipment in self:
|
||||
equipment.maintenance_team_required = (
|
||||
len(
|
||||
equipment.maintenance_plan_ids.filtered(
|
||||
lambda r: not r.maintenance_team_id
|
||||
)
|
||||
)
|
||||
>= 1
|
||||
)
|
||||
|
||||
@api.constrains("company_id", "maintenance_plan_ids")
|
||||
def _check_company_id(self):
|
||||
for rec in self:
|
||||
if rec.company_id and not all(
|
||||
rec.company_id == p.company_id for p in rec.maintenance_plan_ids
|
||||
):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Some maintenance plan's company is incompatible with "
|
||||
"the company of this equipment."
|
||||
)
|
||||
)
|
||||
|
||||
def _prepare_requests_from_plan(self, maintenance_plan, next_maintenance_date):
|
||||
if self:
|
||||
return self._prepare_request_from_plan(
|
||||
maintenance_plan, next_maintenance_date
|
||||
)
|
||||
equipments = maintenance_plan._get_maintenance_equipments()
|
||||
return [
|
||||
equipment._prepare_request_from_plan(
|
||||
maintenance_plan, next_maintenance_date
|
||||
)
|
||||
for equipment in equipments
|
||||
]
|
||||
|
||||
def _prepare_request_from_plan(self, maintenance_plan, next_maintenance_date):
|
||||
team_id = maintenance_plan.maintenance_team_id.id or self.maintenance_team_id.id
|
||||
request_model = self.env["maintenance.request"]
|
||||
if not team_id:
|
||||
team_id = request_model._get_default_team_id()
|
||||
|
||||
description = self.name if self else maintenance_plan.name
|
||||
kind = maintenance_plan.maintenance_kind_id.name or _("Unspecified kind")
|
||||
name = _(
|
||||
"Preventive Maintenance (%(kind)s) - %(description)s",
|
||||
kind=kind,
|
||||
description=description,
|
||||
)
|
||||
|
||||
data = {
|
||||
"name": name,
|
||||
"request_date": next_maintenance_date,
|
||||
"schedule_date": next_maintenance_date,
|
||||
"category_id": self.category_id.id,
|
||||
"equipment_id": self.id,
|
||||
"maintenance_type": "preventive",
|
||||
"owner_user_id": self.owner_user_id.id or self.env.user.id,
|
||||
"user_id": self.technician_user_id.id,
|
||||
"maintenance_team_id": team_id,
|
||||
"maintenance_kind_id": maintenance_plan.maintenance_kind_id.id,
|
||||
"maintenance_plan_id": maintenance_plan.id,
|
||||
"duration": maintenance_plan.duration,
|
||||
"note": maintenance_plan.note,
|
||||
"company_id": maintenance_plan.company_id.id or self.company_id.id,
|
||||
}
|
||||
# This field comes from maintenance_timesheet for avoiding a glue module
|
||||
if "planned_hours" in request_model._fields:
|
||||
data["planned_hours"] = maintenance_plan.duration
|
||||
return data
|
||||
|
||||
def _create_new_request(self, mtn_plan):
|
||||
# Compute horizon date adding to today the planning horizon
|
||||
horizon_date = fields.Date.today() + mtn_plan.get_relativedelta(
|
||||
mtn_plan.maintenance_plan_horizon, mtn_plan.planning_step or "year"
|
||||
)
|
||||
# We check maintenance request already created and create until
|
||||
# planning horizon is met
|
||||
start_maintenance_date_plan = mtn_plan.start_maintenance_date
|
||||
furthest_maintenance_request = self.env["maintenance.request"].search(
|
||||
[
|
||||
("maintenance_plan_id", "=", mtn_plan.id),
|
||||
("request_date", ">=", start_maintenance_date_plan),
|
||||
],
|
||||
order="request_date desc",
|
||||
limit=1,
|
||||
)
|
||||
if furthest_maintenance_request:
|
||||
next_maintenance_date = (
|
||||
furthest_maintenance_request.request_date
|
||||
+ mtn_plan.get_relativedelta(
|
||||
mtn_plan.interval, mtn_plan.interval_step or "year"
|
||||
)
|
||||
)
|
||||
else:
|
||||
next_maintenance_date = mtn_plan.next_maintenance_date
|
||||
skip_notify_follower = mtn_plan.skip_notify_follower_on_requests
|
||||
# Skip assigned mail + Activity mail
|
||||
request_model = self.env["maintenance.request"].with_context(
|
||||
mail_activity_quick_update=skip_notify_follower,
|
||||
mail_auto_subscribe_no_notify=skip_notify_follower,
|
||||
)
|
||||
requests = request_model
|
||||
# Create maintenance request until we reach planning horizon
|
||||
while next_maintenance_date <= horizon_date:
|
||||
if next_maintenance_date >= fields.Date.today():
|
||||
vals = self._prepare_requests_from_plan(mtn_plan, next_maintenance_date)
|
||||
requests |= request_model.create(vals)
|
||||
next_maintenance_date = next_maintenance_date + mtn_plan.get_relativedelta(
|
||||
mtn_plan.interval, mtn_plan.interval_step or "year"
|
||||
)
|
||||
return requests
|
||||
|
||||
@api.model
|
||||
def _cron_generate_requests(self):
|
||||
"""
|
||||
Generates maintenance request on the next_maintenance_date or
|
||||
today if none exists
|
||||
"""
|
||||
for plan in (
|
||||
self.env["maintenance.plan"]
|
||||
.sudo()
|
||||
.search([("interval", ">", 0)])
|
||||
.filtered(lambda x: True if not x.equipment_id else x.equipment_id.active)
|
||||
):
|
||||
equipment = plan.equipment_id
|
||||
equipment._create_new_request(plan)
|
||||
|
||||
@api.depends(
|
||||
"maintenance_plan_ids.next_maintenance_date", "maintenance_ids.request_date"
|
||||
)
|
||||
def _compute_next_maintenance(self):
|
||||
"""Redefine the function to display next_action_date in kanban view"""
|
||||
for equipment in self:
|
||||
next_plan_dates = equipment.maintenance_plan_ids.mapped(
|
||||
"next_maintenance_date"
|
||||
)
|
||||
next_unplanned_dates = (
|
||||
self.env["maintenance.request"]
|
||||
.search(
|
||||
[
|
||||
("equipment_id", "=", equipment.id),
|
||||
("maintenance_kind_id", "=", None),
|
||||
("request_date", ">", fields.Date.context_today(self)),
|
||||
("stage_id.done", "!=", True),
|
||||
("close_date", "=", False),
|
||||
]
|
||||
)
|
||||
.mapped("request_date")
|
||||
)
|
||||
if len(next_plan_dates + next_unplanned_dates) <= 0:
|
||||
equipment.next_action_date = None
|
||||
else:
|
||||
equipment.next_action_date = min(next_plan_dates + next_unplanned_dates)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com).
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MaintenanceKind(models.Model):
|
||||
|
||||
_name = "maintenance.kind"
|
||||
_description = "Maintenance Kind"
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
active = fields.Boolean("Active Kind", required=True, default=True)
|
||||
|
||||
_sql_constraints = [
|
||||
("name_uniq", "unique (name)", "Maintenance kind name already exists.")
|
||||
]
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# Copyright 2019-20 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import safe_eval
|
||||
|
||||
|
||||
class MaintenancePlan(models.Model):
|
||||
_name = "maintenance.plan"
|
||||
_description = "Maintenance Plan"
|
||||
_inherit = ["mail.thread", "mail.activity.mixin"]
|
||||
|
||||
name = fields.Char("Description")
|
||||
active = fields.Boolean(default=True)
|
||||
equipment_id = fields.Many2one(
|
||||
string="Equipment", comodel_name="maintenance.equipment", ondelete="cascade"
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company",
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
maintenance_kind_id = fields.Many2one(
|
||||
string="Maintenance Kind", comodel_name="maintenance.kind", ondelete="restrict"
|
||||
)
|
||||
interval = fields.Integer(
|
||||
string="Frequency", default=1, help="Interval between each maintenance"
|
||||
)
|
||||
interval_step = fields.Selection(
|
||||
[
|
||||
("day", "Day(s)"),
|
||||
("week", "Week(s)"),
|
||||
("month", "Month(s)"),
|
||||
("year", "Year(s)"),
|
||||
],
|
||||
string="Recurrence",
|
||||
default="year",
|
||||
help="Let the event automatically repeat at that interval step",
|
||||
)
|
||||
duration = fields.Float(
|
||||
string="Duration (hours)", help="Maintenance duration in hours"
|
||||
)
|
||||
start_maintenance_date = fields.Date(
|
||||
default=fields.Date.context_today,
|
||||
help="Date from which the maintenance will we active",
|
||||
)
|
||||
next_maintenance_date = fields.Date(compute="_compute_next_maintenance", store=True)
|
||||
maintenance_plan_horizon = fields.Integer(
|
||||
string="Planning Horizon period",
|
||||
default=1,
|
||||
help="Maintenance planning horizon. Only the maintenance requests "
|
||||
"inside the horizon will be created.",
|
||||
)
|
||||
planning_step = fields.Selection(
|
||||
[
|
||||
("day", "Day(s)"),
|
||||
("week", "Week(s)"),
|
||||
("month", "Month(s)"),
|
||||
("year", "Year(s)"),
|
||||
],
|
||||
string="Planning Horizon step",
|
||||
default="year",
|
||||
help="Let the event automatically repeat at that interval",
|
||||
)
|
||||
note = fields.Html()
|
||||
maintenance_ids = fields.One2many(
|
||||
"maintenance.request", "maintenance_plan_id", string="Maintenance requests"
|
||||
)
|
||||
maintenance_count = fields.Integer(
|
||||
compute="_compute_maintenance_count", string="Maintenance", store=True
|
||||
)
|
||||
maintenance_open_count = fields.Integer(
|
||||
compute="_compute_maintenance_count", string="Current Maintenance", store=True
|
||||
)
|
||||
maintenance_team_id = fields.Many2one("maintenance.team")
|
||||
skip_notify_follower_on_requests = fields.Boolean(
|
||||
string="Do not notify to follower when creating requests?", default=True
|
||||
)
|
||||
generate_with_domain = fields.Boolean()
|
||||
generate_domain = fields.Char(string="Apply on")
|
||||
search_equipment_id = fields.Many2one(
|
||||
comodel_name="maintenance.equipment",
|
||||
compute="_compute_search_equipment",
|
||||
search="_search_search_equipment",
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _search_search_equipment(self, operator, value):
|
||||
if operator != "=" or (not value and not isinstance(value, models.NewId)):
|
||||
raise ValueError(_("Unsupported search operator"))
|
||||
plans = self.search([("generate_with_domain", "=", True)])
|
||||
plan_ids = []
|
||||
equipment = self.env["maintenance.equipment"].browse(value)
|
||||
for plan in plans:
|
||||
if equipment.filtered_domain(
|
||||
safe_eval.safe_eval(
|
||||
plan.generate_domain or "[]", plan._get_eval_context()
|
||||
)
|
||||
):
|
||||
plan_ids.append(plan.id)
|
||||
return ["|", ("equipment_id", "=", value), ("id", "in", plan_ids)]
|
||||
|
||||
@api.depends("equipment_id")
|
||||
def _compute_search_equipment(self):
|
||||
for record in self:
|
||||
record.search_equipment_id = record.equipment_id
|
||||
|
||||
def _get_eval_context(self):
|
||||
"""Prepare the context used when evaluating python code
|
||||
:returns: dict -- evaluation context given to safe_eval
|
||||
"""
|
||||
return {
|
||||
"datetime": safe_eval.datetime,
|
||||
"dateutil": safe_eval.dateutil,
|
||||
"time": safe_eval.time,
|
||||
}
|
||||
|
||||
def name_get(self):
|
||||
result = []
|
||||
for plan in self:
|
||||
result.append(
|
||||
(
|
||||
plan.id,
|
||||
plan.name
|
||||
or _(
|
||||
"Unnamed %(kind)s plan (%(eqpmt)s)",
|
||||
kind=plan.maintenance_kind_id.name or "",
|
||||
eqpmt=plan.equipment_id.name,
|
||||
),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
@api.depends("maintenance_ids.stage_id.done")
|
||||
def _compute_maintenance_count(self):
|
||||
for equipment in self:
|
||||
equipment.maintenance_count = len(equipment.maintenance_ids)
|
||||
equipment.maintenance_open_count = len(
|
||||
equipment.maintenance_ids.filtered(lambda x: not x.stage_id.done)
|
||||
)
|
||||
|
||||
def get_relativedelta(self, interval, step):
|
||||
if step == "day":
|
||||
return relativedelta(days=interval)
|
||||
elif step == "week":
|
||||
return relativedelta(weeks=interval)
|
||||
elif step == "month":
|
||||
return relativedelta(months=interval)
|
||||
elif step == "year":
|
||||
return relativedelta(years=interval)
|
||||
|
||||
@api.depends(
|
||||
"interval",
|
||||
"interval_step",
|
||||
"start_maintenance_date",
|
||||
"maintenance_ids.request_date",
|
||||
"maintenance_ids.close_date",
|
||||
)
|
||||
def _compute_next_maintenance(self):
|
||||
for plan in self.filtered(lambda x: x.interval > 0):
|
||||
|
||||
interval_timedelta = plan.get_relativedelta(
|
||||
plan.interval, plan.interval_step
|
||||
)
|
||||
|
||||
next_maintenance_todo = self.env["maintenance.request"].search(
|
||||
[
|
||||
("maintenance_plan_id", "=", plan.id),
|
||||
("stage_id.done", "!=", True),
|
||||
("close_date", "=", False),
|
||||
("request_date", ">=", plan.start_maintenance_date),
|
||||
],
|
||||
order="request_date asc",
|
||||
limit=1,
|
||||
)
|
||||
|
||||
if next_maintenance_todo:
|
||||
plan.next_maintenance_date = next_maintenance_todo.request_date
|
||||
else:
|
||||
last_maintenance_done = self.env["maintenance.request"].search(
|
||||
[
|
||||
("maintenance_plan_id", "=", plan.id),
|
||||
("request_date", ">=", plan.start_maintenance_date),
|
||||
],
|
||||
order="request_date desc",
|
||||
limit=1,
|
||||
)
|
||||
if last_maintenance_done:
|
||||
plan.next_maintenance_date = (
|
||||
last_maintenance_done.request_date + interval_timedelta
|
||||
)
|
||||
else:
|
||||
next_date = plan.start_maintenance_date
|
||||
while next_date < fields.Date.today():
|
||||
next_date = next_date + interval_timedelta
|
||||
plan.next_maintenance_date = next_date
|
||||
|
||||
@api.constrains("company_id", "equipment_id")
|
||||
def _check_company_id(self):
|
||||
for rec in self:
|
||||
if (
|
||||
rec.equipment_id.company_id
|
||||
and rec.company_id != rec.equipment_id.company_id
|
||||
):
|
||||
raise ValidationError(
|
||||
_("Maintenace Equipment must belong to the equipment's company")
|
||||
)
|
||||
|
||||
def unlink(self):
|
||||
"""Restrict deletion of maintenance plan should there be maintenance
|
||||
requests of this kind which are not done for its equipment"""
|
||||
for plan in self:
|
||||
request = plan.equipment_id.mapped("maintenance_ids").filtered(
|
||||
lambda r: (
|
||||
r.maintenance_kind_id == plan.maintenance_kind_id
|
||||
and not r.stage_id.done
|
||||
and r.maintenance_type == "preventive"
|
||||
)
|
||||
)
|
||||
if request:
|
||||
raise UserError(
|
||||
_(
|
||||
"The maintenance plan %(kind)s of equipment %(eqpmnt)s "
|
||||
"has generated a request which is not done "
|
||||
"yet. You should either set the request as "
|
||||
"done, remove its maintenance kind or "
|
||||
"delete it first.",
|
||||
kind=plan.maintenance_kind_id.name,
|
||||
eqpmnt=plan.equipment_id.name,
|
||||
)
|
||||
)
|
||||
return super().unlink()
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"equipment_kind_uniq",
|
||||
"unique (equipment_id, maintenance_kind_id)",
|
||||
"You cannot define multiple times the same maintenance kind on an "
|
||||
"equipment maintenance plan.",
|
||||
)
|
||||
]
|
||||
|
||||
def button_manual_request_generation(self):
|
||||
"""Call the same method that the cron for generating manually the maintenance
|
||||
requests."""
|
||||
for plan in self:
|
||||
equipment = plan.equipment_id
|
||||
equipment._create_new_request(plan)
|
||||
|
||||
def _get_maintenance_equipments(self):
|
||||
self.ensure_one()
|
||||
if self.generate_with_domain and not self.equipment_id:
|
||||
return self.env["maintenance.equipment"].search(
|
||||
safe_eval.safe_eval(
|
||||
self.generate_domain or "[]", self._get_eval_context()
|
||||
)
|
||||
)
|
||||
return [self.equipment_id]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MaintenanceRequest(models.Model):
|
||||
|
||||
_inherit = "maintenance.request"
|
||||
|
||||
maintenance_kind_id = fields.Many2one(
|
||||
string="Maintenance kind", comodel_name="maintenance.kind", ondelete="restrict"
|
||||
)
|
||||
|
||||
maintenance_plan_id = fields.Many2one(
|
||||
string="Maintenance plan", comodel_name="maintenance.plan", ondelete="restrict"
|
||||
)
|
||||
note = fields.Html()
|
||||
Loading…
Add table
Add a link
Reference in a new issue