Начальное наполнение

This commit is contained in:
parent fc7b477fa2
commit 4cd6d33795
100 changed files with 114885 additions and 0 deletions

4
__init__.py Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models

32
__manifest__.py Normal file
View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
{
'name': 'Maintenance',
'version': '1.0',
'sequence': 100,
'category': 'Manufacturing/Maintenance',
'description': """
Track equipment and maintenance requests""",
'depends': ['mail'],
'summary': 'Track equipment and manage maintenance requests',
'website': 'https://www.odoo.com/app/maintenance',
'data': [
'security/maintenance.xml',
'security/ir.model.access.csv',
'data/maintenance_data.xml',
'data/mail_activity_type_data.xml',
'data/mail_message_subtype_data.xml',
'views/maintenance_views.xml',
'views/mail_activity_views.xml',
'views/res_config_settings_views.xml',
],
'demo': ['data/maintenance_demo.xml'],
'installable': True,
'application': True,
'assets': {
'web.assets_backend': [
'maintenance/static/src/**/*',
],
},
'license': 'LGPL-3',
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Maintenance-specific activities, for automatic generation mainly -->
<record id="mail_act_maintenance_request" model="mail.activity.type">
<field name="name">Maintenance Request</field>
<field name="icon">fa-wrench</field>
<field name="res_model">maintenance.request</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Maintenance Request-related subtypes for messaging / Chatter -->
<record id="mt_req_created" model="mail.message.subtype">
<field name="name">Request Created</field>
<field name="res_model">maintenance.request</field>
<field name="default" eval="False"/>
<field name="hidden" eval="True"/>
<field name="description">Maintenance Request created</field>
</record>
<record id="mt_req_status" model="mail.message.subtype">
<field name="name">Status Changed</field>
<field name="res_model">maintenance.request</field>
<field name="default" eval="True"/>
<field name="description">Status changed</field>
</record>
<!-- Equipment-related subtypes for messaging / Chatter -->
<record id="mt_mat_assign" model="mail.message.subtype">
<field name="name">Equipment Assigned</field>
<field name="res_model">maintenance.equipment</field>
<field name="description">Equipment Assigned</field>
</record>
<!-- Equipment Category-related subtypes for messaging / Chatter -->
<record id="mt_cat_req_created" model="mail.message.subtype">
<field name="name">Maintenance Request Created</field>
<field name="res_model">maintenance.equipment.category</field>
<field name="default" eval="True"/>
<field name="parent_id" ref="mt_req_created"/>
<field name="relation_field">category_id</field>
</record>
<record id="mt_cat_mat_assign" model="mail.message.subtype">
<field name="name">Equipment Assigned</field>
<field name="res_model">maintenance.equipment.category</field>
<field name="default" eval="True"/>
<field name="parent_id" ref="mt_mat_assign"/>
<field name="relation_field">category_id</field>
</record>
<record id="equipment_team_maintenance" model="maintenance.team">
<field name="name">Internal Maintenance</field>
</record>
</data>
</odoo>

30
data/maintenance_data.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Standard stages for Maintenance Request -->
<record id="stage_0" model="maintenance.stage">
<field name="name">New Request</field>
<field name="sequence" eval="1" />
<field name="fold" eval="False" />
</record>
<record id="stage_1" model="maintenance.stage">
<field name="name">In Progress</field>
<field name="sequence" eval="2" />
<field name="fold" eval="False" />
</record>
<record id="stage_3" model="maintenance.stage">
<field name="name">Repaired</field>
<field name="sequence" eval="3" />
<field name="fold" eval="True" />
<field name="done" eval="True" />
</record>
<record id="stage_4" model="maintenance.stage">
<field name="name">Scrap</field>
<field name="sequence" eval="4" />
<field name="fold" eval="True" />
<field name="done" eval="True" />
</record>
</data>
</odoo>

149
data/maintenance_demo.xml Normal file
View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Maintenance teams -->
<record id="equipment_team_metrology" model="maintenance.team">
<field name="name">Metrology</field>
</record>
<record id="equipment_team_subcontractor" model="maintenance.team">
<field name="name">Subcontractor</field>
</record>
<!-- Equipment categories -->
<record id="equipment_computer" model="maintenance.equipment.category">
<field name="name">Computers</field>
</record>
<record id="equipment_software" model="maintenance.equipment.category">
<field name="name">Software</field>
</record>
<record id="equipment_printer" model="maintenance.equipment.category">
<field name="name">Printers</field>
</record>
<record id="equipment_monitor" model="maintenance.equipment.category">
<field name="name">Monitors</field>
<field name="technician_user_id" ref="base.user_admin"/>
<field name="color">3</field>
</record>
<record id="equipment_phone" model="maintenance.equipment.category">
<field name="name">Phones</field>
<field name="technician_user_id" ref="base.user_admin"/>
</record>
<!-- Equipment -->
<record id="equipment_monitor1" model="maintenance.equipment">
<field name="name">Samsung Monitor 15"</field>
<field name="category_id" ref="equipment_monitor"/>
<field name="owner_user_id" ref="base.user_admin"/>
<field name="technician_user_id" ref="base.user_admin"/>
<field name="assign_date" eval="time.strftime('%Y-%m-10')"/>
<field name="serial_no">MT/122/11112222</field>
<field name="model">NP300E5X</field>
</record>
<record id="equipment_monitor4" model="maintenance.equipment">
<field name="name">Samsung Monitor 15"</field>
<field name="category_id" ref="equipment_monitor"/>
<field name="owner_user_id" ref="base.user_admin"/>
<field name="technician_user_id" ref="base.user_admin"/>
<field name="assign_date" eval="time.strftime('%Y-01-01')"/>
<field name="serial_no">MT/125/22778837</field>
<field name="model">NP355E5X</field>
</record>
<record id="equipment_monitor6" model="maintenance.equipment">
<field name="name">Samsung Monitor 15"</field>
<field name="category_id" ref="equipment_monitor"/>
<field name="owner_user_id" ref="base.user_demo"/>
<field name="technician_user_id" ref="base.user_demo"/>
<field name="assign_date" eval="time.strftime('%Y-02-01')"/>
<field name="serial_no">MT/127/18291018</field>
<field name="model">NP355E5X</field>
<field name="color">3</field>
</record>
<record id="equipment_computer3" model="maintenance.equipment">
<field name="name">Acer Laptop</field>
<field name="category_id" ref="equipment_computer"/>
<field name="owner_user_id" ref="base.user_demo"/>
<field name="technician_user_id" ref="base.user_admin"/>
<field name="assign_date" eval="time.strftime('%Y-03-08')"/>
<field name="serial_no">LP/203/19281928</field>
<field name="model">NE56R</field>
</record>
<record id="equipment_computer5" model="maintenance.equipment">
<field name="name">Acer Laptop</field>
<field name="category_id" ref="equipment_computer"/>
<field name="owner_user_id" ref="base.user_admin"/>
<field name="technician_user_id" ref="base.user_demo"/>
<field name="assign_date" eval="time.strftime('%Y-04-08')"/>
<field name="serial_no">LP/205/12928291</field>
<field name="model">V5131</field>
</record>
<record id="equipment_computer9" model="maintenance.equipment">
<field name="name">HP Laptop</field>
<field name="category_id" ref="equipment_computer"/>
<field name="owner_user_id" ref="base.user_admin"/>
<field name="technician_user_id" ref="base.user_demo"/>
<field name="assign_date" eval="time.strftime('%Y-%m-11')"/>
<field name="serial_no">LP/303/28292090</field>
<field name="model">17-j059nr</field>
</record>
<record id="equipment_computer11" model="maintenance.equipment">
<field name="name">HP Laptop</field>
<field name="category_id" ref="equipment_computer"/>
<field name="owner_user_id" ref="base.user_demo"/>
<field name="technician_user_id" ref="base.user_demo"/>
<field name="assign_date" eval="time.strftime('%Y-05-01')"/>
<field name="serial_no">LP/305/17281718</field>
</record>
<record id="equipment_printer1" model="maintenance.equipment">
<field name="name">HP Inkjet printer</field>
<field name="category_id" ref="equipment_printer"/>
<field name="technician_user_id" ref="base.user_demo"/>
<field name="serial_no">PR/011/2928191889</field>
</record>
<!--Maintenance Request-->
<record id="m_request_3" model="maintenance.request">
<field name="name">Resolution is bad</field>
<field name="user_id" ref="base.user_demo"/>
<field name="owner_user_id" ref="base.user_admin"/>
<field name="equipment_id" ref="equipment_monitor6"/>
<field name="color">7</field>
<field name="stage_id" ref="stage_3"/>
<field name="maintenance_team_id" ref="equipment_team_maintenance"/>
</record>
<record id="m_request_4" model="maintenance.request">
<field name="name">Some keys are not working</field>
<field name="user_id" ref="base.user_admin"/>
<field name="owner_user_id" ref="base.user_admin"/>
<field name="equipment_id" ref="equipment_computer3"/>
<field name="stage_id" ref="stage_0"/>
<field name="maintenance_team_id" ref="equipment_team_maintenance"/>
</record>
<record id="m_request_6" model="maintenance.request">
<field name="name">Motherboard failed</field>
<field name="user_id" ref="base.user_demo"/>
<field name="owner_user_id" ref="base.user_admin"/>
<field name="equipment_id" ref="equipment_computer5"/>
<field name="stage_id" ref="stage_4"/>
<field name="maintenance_team_id" ref="equipment_team_maintenance"/>
</record>
<record id="m_request_7" model="maintenance.request">
<field name="name">Battery drains fast</field>
<field name="user_id" ref="base.user_demo"/>
<field name="owner_user_id" ref="base.user_demo"/>
<field name="equipment_id" ref="equipment_computer9"/>
<field name="stage_id" ref="stage_1"/>
<field name="maintenance_team_id" ref="equipment_team_maintenance"/>
</record>
<record id="m_request_8" model="maintenance.request">
<field name="name">Touchpad not working</field>
<field name="user_id" ref="base.user_demo"/>
<field name="owner_user_id" ref="base.user_demo"/>
<field name="equipment_id" ref="equipment_computer11"/>
<field name="stage_id" ref="stage_1"/>
<field name="maintenance_team_id" ref="equipment_team_maintenance"/>
</record>
</data>
</odoo>

1412
i18n/af.po Normal file

File diff suppressed because it is too large Load Diff

1408
i18n/am.po Normal file

File diff suppressed because it is too large Load Diff

1669
i18n/ar.po Normal file

File diff suppressed because it is too large Load Diff

1421
i18n/az.po Normal file

File diff suppressed because it is too large Load Diff

1679
i18n/bg.po Normal file

File diff suppressed because it is too large Load Diff

1413
i18n/bs.po Normal file

File diff suppressed because it is too large Load Diff

1695
i18n/ca.po Normal file

File diff suppressed because it is too large Load Diff

1673
i18n/cs.po Normal file

File diff suppressed because it is too large Load Diff

1674
i18n/da.po Normal file

File diff suppressed because it is too large Load Diff

1682
i18n/de.po Normal file

File diff suppressed because it is too large Load Diff

1425
i18n/el.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/en_GB.po Normal file

File diff suppressed because it is too large Load Diff

1682
i18n/es.po Normal file

File diff suppressed because it is too large Load Diff

1685
i18n/es_419.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_BO.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_CL.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_CO.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_CR.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_DO.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_EC.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_PE.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_PY.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/es_VE.po Normal file

File diff suppressed because it is too large Load Diff

1686
i18n/et.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/eu.po Normal file

File diff suppressed because it is too large Load Diff

1677
i18n/fa.po Normal file

File diff suppressed because it is too large Load Diff

1689
i18n/fi.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/fo.po Normal file

File diff suppressed because it is too large Load Diff

1680
i18n/fr.po Normal file

File diff suppressed because it is too large Load Diff

1410
i18n/fr_BE.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/fr_CA.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/gl.po Normal file

File diff suppressed because it is too large Load Diff

1416
i18n/gu.po Normal file

File diff suppressed because it is too large Load Diff

1677
i18n/he.po Normal file

File diff suppressed because it is too large Load Diff

1410
i18n/hi.po Normal file

File diff suppressed because it is too large Load Diff

1434
i18n/hr.po Normal file

File diff suppressed because it is too large Load Diff

1669
i18n/hu.po Normal file

File diff suppressed because it is too large Load Diff

1674
i18n/id.po Normal file

File diff suppressed because it is too large Load Diff

1412
i18n/is.po Normal file

File diff suppressed because it is too large Load Diff

1675
i18n/it.po Normal file

File diff suppressed because it is too large Load Diff

1648
i18n/ja.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/ka.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/kab.po Normal file

File diff suppressed because it is too large Load Diff

1410
i18n/kk.po Normal file

File diff suppressed because it is too large Load Diff

1413
i18n/km.po Normal file

File diff suppressed because it is too large Load Diff

1653
i18n/ko.po Normal file

File diff suppressed because it is too large Load Diff

1412
i18n/lb.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/lo.po Normal file

File diff suppressed because it is too large Load Diff

1682
i18n/lt.po Normal file

File diff suppressed because it is too large Load Diff

1666
i18n/lv.po Normal file

File diff suppressed because it is too large Load Diff

1631
i18n/maintenance.pot Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/mk.po Normal file

File diff suppressed because it is too large Load Diff

1431
i18n/mn.po Normal file

File diff suppressed because it is too large Load Diff

1421
i18n/nb.po Normal file

File diff suppressed because it is too large Load Diff

1408
i18n/ne.po Normal file

File diff suppressed because it is too large Load Diff

1678
i18n/nl.po Normal file

File diff suppressed because it is too large Load Diff

1671
i18n/pl.po Normal file

File diff suppressed because it is too large Load Diff

1663
i18n/pt.po Normal file

File diff suppressed because it is too large Load Diff

1677
i18n/pt_BR.po Normal file

File diff suppressed because it is too large Load Diff

1424
i18n/ro.po Normal file

File diff suppressed because it is too large Load Diff

1689
i18n/ru.po Normal file

File diff suppressed because it is too large Load Diff

1665
i18n/sk.po Normal file

File diff suppressed because it is too large Load Diff

1676
i18n/sl.po Normal file

File diff suppressed because it is too large Load Diff

1411
i18n/sq.po Normal file

File diff suppressed because it is too large Load Diff

1675
i18n/sr.po Normal file

File diff suppressed because it is too large Load Diff

1414
i18n/sr@latin.po Normal file

File diff suppressed because it is too large Load Diff

1680
i18n/sv.po Normal file

File diff suppressed because it is too large Load Diff

1667
i18n/th.po Normal file

File diff suppressed because it is too large Load Diff

1690
i18n/tr.po Normal file

File diff suppressed because it is too large Load Diff

1675
i18n/uk.po Normal file

File diff suppressed because it is too large Load Diff

1669
i18n/vi.po Normal file

File diff suppressed because it is too large Load Diff

1652
i18n/zh_CN.po Normal file

File diff suppressed because it is too large Load Diff

1648
i18n/zh_TW.po Normal file

File diff suppressed because it is too large Load Diff

4
models/__init__.py Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import maintenance
from . import res_config_settings

425
models/maintenance.py Normal file
View File

@ -0,0 +1,425 @@
# -*- coding: utf-8 -*-
import ast
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import UserError
from odoo.osv import expression
class MaintenanceStage(models.Model):
""" Model for case stages. This models the main stages of a Maintenance Request management flow. """
_name = 'maintenance.stage'
_description = 'Maintenance Stage'
_order = 'sequence, id'
name = fields.Char('Name', required=True, translate=True)
sequence = fields.Integer('Sequence', default=20)
fold = fields.Boolean('Folded in Maintenance Pipe')
done = fields.Boolean('Request Done')
class MaintenanceEquipmentCategory(models.Model):
_name = 'maintenance.equipment.category'
_inherit = ['mail.alias.mixin', 'mail.thread']
_description = 'Maintenance Equipment Category'
@api.depends('equipment_ids')
def _compute_fold(self):
# fix mutual dependency: 'fold' depends on 'equipment_count', which is
# computed with a read_group(), which retrieves 'fold'!
self.fold = False
for category in self:
category.fold = False if category.equipment_count else True
name = fields.Char('Category Name', required=True, translate=True)
company_id = fields.Many2one('res.company', string='Company',
default=lambda self: self.env.company)
technician_user_id = fields.Many2one('res.users', 'Responsible', tracking=True, default=lambda self: self.env.uid)
color = fields.Integer('Color Index')
note = fields.Html('Comments', translate=True)
equipment_ids = fields.One2many('maintenance.equipment', 'category_id', string='Equipment', copy=False)
equipment_count = fields.Integer(string="Equipment Count", compute='_compute_equipment_count')
maintenance_ids = fields.One2many('maintenance.request', 'category_id', copy=False)
maintenance_count = fields.Integer(string="Maintenance Count", compute='_compute_maintenance_count')
maintenance_open_count = fields.Integer(string="Current Maintenance", compute='_compute_maintenance_count')
alias_id = fields.Many2one(help="Email alias for this equipment category. New emails will automatically "
"create a new equipment under this category.")
fold = fields.Boolean(string='Folded in Maintenance Pipe', compute='_compute_fold', store=True)
def _compute_equipment_count(self):
equipment_data = self.env['maintenance.equipment']._read_group([('category_id', 'in', self.ids)], ['category_id'], ['__count'])
mapped_data = {category.id: count for category, count in equipment_data}
for category in self:
category.equipment_count = mapped_data.get(category.id, 0)
def _compute_maintenance_count(self):
maintenance_data = self.env['maintenance.request']._read_group([('category_id', 'in', self.ids)], ['category_id', 'archive'], ['__count'])
mapped_data = {(category.id, archive): count for category, archive, count in maintenance_data}
for category in self:
category.maintenance_open_count = mapped_data.get((category.id, False), 0)
category.maintenance_count = category.maintenance_open_count + mapped_data.get((category.id, True), 0)
@api.ondelete(at_uninstall=False)
def _unlink_except_contains_maintenance_requests(self):
for category in self:
if category.equipment_ids or category.maintenance_ids:
raise UserError(_("You cannot delete an equipment category containing equipment or maintenance requests."))
def _alias_get_creation_values(self):
values = super(MaintenanceEquipmentCategory, self)._alias_get_creation_values()
values['alias_model_id'] = self.env['ir.model']._get('maintenance.request').id
if self.id:
values['alias_defaults'] = defaults = ast.literal_eval(self.alias_defaults or "{}")
defaults['category_id'] = self.id
return values
class MaintenanceMixin(models.AbstractModel):
_name = 'maintenance.mixin'
_check_company_auto = True
_description = 'Maintenance Maintained Item'
company_id = fields.Many2one('res.company', string='Company',
default=lambda self: self.env.company)
effective_date = fields.Date('Effective Date', default=fields.Date.context_today, required=True, help="This date will be used to compute the Mean Time Between Failure.")
maintenance_team_id = fields.Many2one('maintenance.team', string='Maintenance Team', compute='_compute_maintenance_team_id', store=True, readonly=False, check_company=True)
technician_user_id = fields.Many2one('res.users', string='Technician', tracking=True)
maintenance_ids = fields.One2many('maintenance.request') # needs to be extended in order to specify inverse_name !
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Maintenance Count", store=True)
maintenance_open_count = fields.Integer(compute='_compute_maintenance_count', string="Current Maintenance", store=True)
expected_mtbf = fields.Integer(string='Expected MTBF', help='Expected Mean Time Between Failure')
mtbf = fields.Integer(compute='_compute_maintenance_request', string='MTBF', help='Mean Time Between Failure, computed based on done corrective maintenances.')
mttr = fields.Integer(compute='_compute_maintenance_request', string='MTTR', help='Mean Time To Repair')
estimated_next_failure = fields.Date(compute='_compute_maintenance_request', string='Estimated time before next failure (in days)', help='Computed as Latest Failure Date + MTBF')
latest_failure_date = fields.Date(compute='_compute_maintenance_request', string='Latest Failure Date')
@api.depends('company_id')
def _compute_maintenance_team_id(self):
for record in self:
if record.maintenance_team_id.company_id and record.maintenance_team_id.company_id.id != record.company_id.id:
record.maintenance_team_id = False
@api.depends('effective_date', 'maintenance_ids.stage_id', 'maintenance_ids.close_date', 'maintenance_ids.request_date')
def _compute_maintenance_request(self):
for record in self:
maintenance_requests = record.maintenance_ids.filtered(lambda mr: mr.maintenance_type == 'corrective' and mr.stage_id.done)
record.mttr = len(maintenance_requests) and (sum(int((request.close_date - request.request_date).days) for request in maintenance_requests) / len(maintenance_requests)) or 0
record.latest_failure_date = max((request.request_date for request in maintenance_requests), default=False)
record.mtbf = record.latest_failure_date and (record.latest_failure_date - record.effective_date).days / len(maintenance_requests) or 0
record.estimated_next_failure = record.mtbf and record.latest_failure_date + relativedelta(days=record.mtbf) or False
@api.depends('maintenance_ids.stage_id.done', 'maintenance_ids.archive')
def _compute_maintenance_count(self):
for record in self:
record.maintenance_count = len(record.maintenance_ids)
record.maintenance_open_count = len(record.maintenance_ids.filtered(lambda mr: not mr.stage_id.done and not mr.archive))
class MaintenanceEquipment(models.Model):
_name = 'maintenance.equipment'
_inherit = ['mail.thread', 'mail.activity.mixin', 'maintenance.mixin']
_description = 'Maintenance Equipment'
_check_company_auto = True
def _track_subtype(self, init_values):
self.ensure_one()
if 'owner_user_id' in init_values and self.owner_user_id:
return self.env.ref('maintenance.mt_mat_assign')
return super(MaintenanceEquipment, self)._track_subtype(init_values)
@api.depends('serial_no')
def _compute_display_name(self):
for record in self:
if record.serial_no:
record.display_name = record.name + '/' + record.serial_no
else:
record.display_name = record.name
@api.model
def _name_search(self, name, domain=None, operator='ilike', limit=None, order=None):
domain = domain or []
query = None
if name and operator not in expression.NEGATIVE_TERM_OPERATORS and operator != '=':
query = self._search([('name', '=', name)] + domain, limit=limit, order=order)
return query or super()._name_search(name, domain, operator, limit, order)
name = fields.Char('Equipment Name', required=True, translate=True)
active = fields.Boolean(default=True)
owner_user_id = fields.Many2one('res.users', string='Owner', tracking=True)
category_id = fields.Many2one('maintenance.equipment.category', string='Equipment Category',
tracking=True, group_expand='_read_group_category_ids')
partner_id = fields.Many2one('res.partner', string='Vendor', check_company=True)
partner_ref = fields.Char('Vendor Reference')
location = fields.Char('Location')
model = fields.Char('Model')
serial_no = fields.Char('Serial Number', copy=False)
assign_date = fields.Date('Assigned Date', tracking=True)
cost = fields.Float('Cost')
note = fields.Html('Note')
warranty_date = fields.Date('Warranty Expiration Date')
color = fields.Integer('Color Index')
scrap_date = fields.Date('Scrap Date')
maintenance_ids = fields.One2many('maintenance.request', 'equipment_id')
@api.onchange('category_id')
def _onchange_category_id(self):
self.technician_user_id = self.category_id.technician_user_id
_sql_constraints = [
('serial_no', 'unique(serial_no)', "Another asset already exists with this serial number!"),
]
@api.model_create_multi
def create(self, vals_list):
equipments = super().create(vals_list)
for equipment in equipments:
if equipment.owner_user_id:
equipment.message_subscribe(partner_ids=[equipment.owner_user_id.partner_id.id])
return equipments
def write(self, vals):
if vals.get('owner_user_id'):
self.message_subscribe(partner_ids=self.env['res.users'].browse(vals['owner_user_id']).partner_id.ids)
return super(MaintenanceEquipment, self).write(vals)
@api.model
def _read_group_category_ids(self, categories, domain, order):
""" Read group customization in order to display all the categories in
the kanban view, even if they are empty.
"""
category_ids = categories._search([], order=order, access_rights_uid=SUPERUSER_ID)
return categories.browse(category_ids)
class MaintenanceRequest(models.Model):
_name = 'maintenance.request'
_inherit = ['mail.thread.cc', 'mail.activity.mixin']
_description = 'Maintenance Request'
_order = "id desc"
_check_company_auto = True
@api.returns('self')
def _default_stage(self):
return self.env['maintenance.stage'].search([], limit=1)
def _creation_subtype(self):
return self.env.ref('maintenance.mt_req_created')
def _track_subtype(self, init_values):
self.ensure_one()
if 'stage_id' in init_values:
return self.env.ref('maintenance.mt_req_status')
return super(MaintenanceRequest, self)._track_subtype(init_values)
def _get_default_team_id(self):
MT = self.env['maintenance.team']
team = MT.search([('company_id', '=', self.env.company.id)], limit=1)
if not team:
team = MT.search([], limit=1)
return team.id
name = fields.Char('Subjects', required=True)
company_id = fields.Many2one('res.company', string='Company', required=True,
default=lambda self: self.env.company)
description = fields.Html('Description')
request_date = fields.Date('Request Date', tracking=True, default=fields.Date.context_today,
help="Date requested for the maintenance to happen")
owner_user_id = fields.Many2one('res.users', string='Created by User', default=lambda s: s.env.uid)
category_id = fields.Many2one('maintenance.equipment.category', related='equipment_id.category_id', string='Category', store=True, readonly=True)
equipment_id = fields.Many2one('maintenance.equipment', string='Equipment',
ondelete='restrict', index=True, check_company=True)
user_id = fields.Many2one('res.users', string='Technician', compute='_compute_user_id', store=True, readonly=False, tracking=True)
stage_id = fields.Many2one('maintenance.stage', string='Stage', ondelete='restrict', tracking=True,
group_expand='_read_group_stage_ids', default=_default_stage, copy=False)
priority = fields.Selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High')], string='Priority')
color = fields.Integer('Color Index')
close_date = fields.Date('Close Date', help="Date the maintenance was finished. ")
kanban_state = fields.Selection([('normal', 'In Progress'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')],
string='Kanban State', required=True, default='normal', tracking=True)
# active = fields.Boolean(default=True, help="Set active to false to hide the maintenance request without deleting it.")
archive = fields.Boolean(default=False, help="Set archive to true to hide the maintenance request without deleting it.")
maintenance_type = fields.Selection([('corrective', 'Corrective'), ('preventive', 'Preventive')], string='Maintenance Type', default="corrective")
schedule_date = fields.Datetime('Scheduled Date', help="Date the maintenance team plans the maintenance. It should not differ much from the Request Date. ")
maintenance_team_id = fields.Many2one('maintenance.team', string='Team', required=True, default=_get_default_team_id,
compute='_compute_maintenance_team_id', store=True, readonly=False, check_company=True)
duration = fields.Float(help="Duration in hours.")
done = fields.Boolean(related='stage_id.done')
instruction_type = fields.Selection([
('pdf', 'PDF'), ('google_slide', 'Google Slide'), ('text', 'Text')],
string="Instruction", default="text"
)
instruction_pdf = fields.Binary('PDF')
instruction_google_slide = fields.Char('Google Slide', help="Paste the url of your Google Slide. Make sure the access to the document is public.")
instruction_text = fields.Html('Text')
recurring_maintenance = fields.Boolean(string="Recurrent", compute='_compute_recurring_maintenance', store=True, readonly=False)
repeat_interval = fields.Integer(string='Repeat Every', default=1)
repeat_unit = fields.Selection([
('day', 'Days'),
('week', 'Weeks'),
('month', 'Months'),
('year', 'Years'),
], default='week')
repeat_type = fields.Selection([
('forever', 'Forever'),
('until', 'Until'),
], default="forever", string="Until")
repeat_until = fields.Date(string="End Date")
def archive_equipment_request(self):
self.write({'archive': True, 'recurring_maintenance': False})
def reset_equipment_request(self):
""" Reinsert the maintenance request into the maintenance pipe in the first stage"""
first_stage_obj = self.env['maintenance.stage'].search([], order="sequence asc", limit=1)
# self.write({'active': True, 'stage_id': first_stage_obj.id})
self.write({'archive': False, 'stage_id': first_stage_obj.id})
@api.depends('company_id', 'equipment_id')
def _compute_maintenance_team_id(self):
for request in self:
if request.equipment_id and request.equipment_id.maintenance_team_id:
request.maintenance_team_id = request.equipment_id.maintenance_team_id.id
if request.maintenance_team_id.company_id and request.maintenance_team_id.company_id.id != request.company_id.id:
request.maintenance_team_id = False
@api.depends('company_id', 'equipment_id')
def _compute_user_id(self):
for request in self:
if request.equipment_id:
request.user_id = request.equipment_id.technician_user_id or request.equipment_id.category_id.technician_user_id
if request.user_id and request.company_id.id not in request.user_id.company_ids.ids:
request.user_id = False
@api.depends('maintenance_type')
def _compute_recurring_maintenance(self):
for request in self:
if request.maintenance_type != 'preventive':
request.recurring_maintenance = False
@api.model_create_multi
def create(self, vals_list):
# context: no_log, because subtype already handle this
maintenance_requests = super().create(vals_list)
for request in maintenance_requests:
if request.owner_user_id or request.user_id:
request._add_followers()
if request.equipment_id and not request.maintenance_team_id:
request.maintenance_team_id = request.equipment_id.maintenance_team_id
if request.close_date and not request.stage_id.done:
request.close_date = False
if not request.close_date and request.stage_id.done:
request.close_date = fields.Date.today()
maintenance_requests.activity_update()
return maintenance_requests
def write(self, vals):
# Overridden to reset the kanban_state to normal whenever
# the stage (stage_id) of the Maintenance Request changes.
if vals and 'kanban_state' not in vals and 'stage_id' in vals:
vals['kanban_state'] = 'normal'
if 'stage_id' in vals and self.maintenance_type == 'preventive' and self.recurring_maintenance and self.env['maintenance.stage'].browse(vals['stage_id']).done:
schedule_date = self.schedule_date or fields.Datetime.now()
schedule_date += relativedelta(**{f"{self.repeat_unit}s": self.repeat_interval})
if self.repeat_type == 'forever' or schedule_date.date() <= self.repeat_until:
self.copy({'schedule_date': schedule_date})
res = super(MaintenanceRequest, self).write(vals)
if vals.get('owner_user_id') or vals.get('user_id'):
self._add_followers()
if 'stage_id' in vals:
self.filtered(lambda m: m.stage_id.done).write({'close_date': fields.Date.today()})
self.filtered(lambda m: not m.stage_id.done).write({'close_date': False})
self.activity_feedback(['maintenance.mail_act_maintenance_request'])
self.activity_update()
if vals.get('user_id') or vals.get('schedule_date'):
self.activity_update()
if self._need_new_activity(vals):
# need to change description of activity also so unlink old and create new activity
self.activity_unlink(['maintenance.mail_act_maintenance_request'])
self.activity_update()
return res
def _need_new_activity(self, vals):
return vals.get('equipment_id')
def _get_activity_note(self):
self.ensure_one()
if self.equipment_id:
return _('Request planned for %s', self.equipment_id._get_html_link())
return False
def activity_update(self):
""" Update maintenance activities based on current record set state.
It reschedule, unlink or create maintenance request activities. """
self.filtered(lambda request: not request.schedule_date).activity_unlink(['maintenance.mail_act_maintenance_request'])
for request in self.filtered(lambda request: request.schedule_date):
date_dl = fields.Datetime.from_string(request.schedule_date).date()
updated = request.activity_reschedule(
['maintenance.mail_act_maintenance_request'],
date_deadline=date_dl,
new_user_id=request.user_id.id or request.owner_user_id.id or self.env.uid)
if not updated:
note = self._get_activity_note()
request.activity_schedule(
'maintenance.mail_act_maintenance_request',
fields.Datetime.from_string(request.schedule_date).date(),
note=note, user_id=request.user_id.id or request.owner_user_id.id or self.env.uid)
def _add_followers(self):
for request in self:
partner_ids = (request.owner_user_id.partner_id + request.user_id.partner_id).ids
request.message_subscribe(partner_ids=partner_ids)
@api.model
def _read_group_stage_ids(self, stages, domain, order):
""" Read group customization in order to display all the stages in the
kanban view, even if they are empty
"""
stage_ids = stages._search([], order=order, access_rights_uid=SUPERUSER_ID)
return stages.browse(stage_ids)
class MaintenanceTeam(models.Model):
_name = 'maintenance.team'
_description = 'Maintenance Teams'
name = fields.Char('Team Name', required=True, translate=True)
active = fields.Boolean(default=True)
company_id = fields.Many2one('res.company', string='Company',
default=lambda self: self.env.company)
member_ids = fields.Many2many(
'res.users', 'maintenance_team_users_rel', string="Team Members",
domain="[('company_ids', 'in', company_id)]")
color = fields.Integer("Color Index", default=0)
request_ids = fields.One2many('maintenance.request', 'maintenance_team_id', copy=False)
equipment_ids = fields.One2many('maintenance.equipment', 'maintenance_team_id', copy=False)
# For the dashboard only
todo_request_ids = fields.One2many('maintenance.request', string="Requests", copy=False, compute='_compute_todo_requests')
todo_request_count = fields.Integer(string="Number of Requests", compute='_compute_todo_requests')
todo_request_count_date = fields.Integer(string="Number of Requests Scheduled", compute='_compute_todo_requests')
todo_request_count_high_priority = fields.Integer(string="Number of Requests in High Priority", compute='_compute_todo_requests')
todo_request_count_block = fields.Integer(string="Number of Requests Blocked", compute='_compute_todo_requests')
todo_request_count_unscheduled = fields.Integer(string="Number of Requests Unscheduled", compute='_compute_todo_requests')
@api.depends('request_ids.stage_id.done')
def _compute_todo_requests(self):
for team in self:
team.todo_request_ids = self.env['maintenance.request'].search([('maintenance_team_id', '=', team.id), ('stage_id.done', '=', False), ('archive', '=', False)])
data = self.env['maintenance.request']._read_group(
[('maintenance_team_id', '=', team.id), ('stage_id.done', '=', False), ('archive', '=', False)],
['schedule_date:year', 'priority', 'kanban_state'],
['__count']
)
team.todo_request_count = sum(count for (_, _, _, count) in data)
team.todo_request_count_date = sum(count for (schedule_date, _, _, count) in data if schedule_date)
team.todo_request_count_high_priority = sum(count for (_, priority, _, count) in data if priority == 3)
team.todo_request_count_block = sum(count for (_, _, kanban_state, count) in data if kanban_state == 'blocked')
team.todo_request_count_unscheduled = team.todo_request_count - team.todo_request_count_date
@api.depends('equipment_ids')
def _compute_equipment(self):
for team in self:
team.equipment_count = len(team.equipment_ids)

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
module_maintenance_worksheet = fields.Boolean(string="Custom Maintenance Worksheets")

View File

@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_equipment_user,equipment.user,model_maintenance_equipment,base.group_user,1,0,0,0
access_equipment_admin_user,equipment.admin.user,model_maintenance_equipment,group_equipment_manager,1,1,1,1
access_maintenance_system_user,equipment.request system user,model_maintenance_request,base.group_user,1,1,1,1
access_equipment_category_user,equipment.category.user,model_maintenance_equipment_category,base.group_user,1,0,0,0
access_equipment_category_admin_user,equipment.category system user,model_maintenance_equipment_category,group_equipment_manager,1,1,1,1
access_maintenance_stage_user,maintenance.stage.user,model_maintenance_stage,base.group_user,1,0,0,0
access_maintenance_stage_admin_user,equipment.request.stage system user,model_maintenance_stage,group_equipment_manager,1,1,1,1
access_maintenance_team_user,maintenance.team.user,model_maintenance_team,base.group_user,1,0,0,0
access_maintenance_team_admin_user,maintenance.team.admin.user,model_maintenance_team,group_equipment_manager,1,1,1,1
access_mail_activity_type_equipment_manager,mail.activity.type.equipment.manager,mail.model_mail_activity_type,maintenance.group_equipment_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_equipment_user equipment.user model_maintenance_equipment base.group_user 1 0 0 0
3 access_equipment_admin_user equipment.admin.user model_maintenance_equipment group_equipment_manager 1 1 1 1
4 access_maintenance_system_user equipment.request system user model_maintenance_request base.group_user 1 1 1 1
5 access_equipment_category_user equipment.category.user model_maintenance_equipment_category base.group_user 1 0 0 0
6 access_equipment_category_admin_user equipment.category system user model_maintenance_equipment_category group_equipment_manager 1 1 1 1
7 access_maintenance_stage_user maintenance.stage.user model_maintenance_stage base.group_user 1 0 0 0
8 access_maintenance_stage_admin_user equipment.request.stage system user model_maintenance_stage group_equipment_manager 1 1 1 1
9 access_maintenance_team_user maintenance.team.user model_maintenance_team base.group_user 1 0 0 0
10 access_maintenance_team_admin_user maintenance.team.admin.user model_maintenance_team group_equipment_manager 1 1 1 1
11 access_mail_activity_type_equipment_manager mail.activity.type.equipment.manager mail.model_mail_activity_type maintenance.group_equipment_manager 1 1 1 1

69
security/maintenance.xml Normal file
View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- This group is only allowed to deal with equipment registration and maintenance -->
<record id="group_equipment_manager" model="res.groups">
<field name="name">Equipment Manager</field>
<field name="category_id" ref="base.module_category_manufacturing_maintenance"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="comment">The user will be able to manage equipment.</field>
</record>
<data noupdate="1">
<!-- Rules -->
<record id="equipment_request_rule_user" model="ir.rule">
<field name="name">Users are allowed to access their own maintenance requests</field>
<field name="model_id" ref="model_maintenance_request"/>
<field name="domain_force">['|', '|', ('owner_user_id', '=', user.id), ('message_partner_ids', 'in', [user.partner_id.id]), ('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="equipment_rule_user" model="ir.rule">
<field name="name">Users are allowed to access equipment they follow</field>
<field name="model_id" ref="model_maintenance_equipment"/>
<field name="domain_force">[('message_partner_ids', 'in', [user.partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="equipment_request_rule_admin_user" model="ir.rule">
<field name="name">Administrator of maintenance requests</field>
<field name="model_id" ref="model_maintenance_request"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_equipment_manager'))]"/>
</record>
<record id="equipment_rule_admin_user" model="ir.rule">
<field name="name">Equipment administrator</field>
<field name="model_id" ref="model_maintenance_equipment"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_equipment_manager'))]"/>
</record>
<record id="maintenance_request_comp_rule" model="ir.rule">
<field name="name">Maintenance Request Multi-company rule</field>
<field name="model_id" ref="model_maintenance_request"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="maintenance_equipment_comp_rule" model="ir.rule">
<field name="name">Maintenance Equipment Multi-company rule</field>
<field name="model_id" ref="model_maintenance_equipment"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="maintenance_team_comp_rule" model="ir.rule">
<field name="name">Maintenance Team Multi-company rule</field>
<field name="model_id" ref="model_maintenance_team"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="maintenance_equipment_category_comp_rule" model="ir.rule">
<field name="name">Maintenance Equipment Category Multi-company rule</field>
<field name="model_id" ref="model_maintenance_equipment_category"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>

BIN
static/description/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1 @@
<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M12.172 24.193s6.121-2.586 7.778-4.243c1.657-1.657 4.243-7.778 4.243-7.778l13.435 13.435s-6.099 2.563-7.779 4.242c-1.68 1.68-4.242 7.779-4.242 7.779L12.172 24.193Z" fill="#2EBCFA"/><path fill-rule="evenodd" clip-rule="evenodd" d="M42.577 22.778c4.296-4.296 4.296-11.26 0-15.556-4.296-4.296-11.26-4.296-15.556 0-4.296 4.296-4.296 11.26 0 15.556 4.295 4.296 11.26 4.296 15.556 0Zm-4.243-4.242a5 5 0 1 0-7.07-7.072 5 5 0 0 0 7.07 7.071ZM22.778 42.577c4.296-4.296 4.296-11.26 0-15.556-4.296-4.296-11.26-4.296-15.556 0-4.296 4.295-4.296 11.26 0 15.556 4.296 4.296 11.26 4.296 15.556 0Zm-4.242-4.243a5 5 0 1 0-7.071-7.07 5 5 0 0 0 7.07 7.07Z" fill="#088BF5"/></svg>

After

Width:  |  Height:  |  Size: 752 B

View File

@ -0,0 +1,3 @@
.o_maintenance_team_kanban .o_kanban_renderer {
--KanbanRecord-width: 420px;
}

View File

@ -0,0 +1,14 @@
/** @odoo-module **/
import { CalendarCommonPopover } from "@web/views/calendar/calendar_common/calendar_common_popover";
export class CalendarWithRecurrenceCommonPopover extends CalendarCommonPopover {
onEditEvent() {
this.props.record.id = this.props.record.rawRecord.id;
super.onEditEvent();
}
onDeleteEvent() {
this.props.record.id = this.props.record.rawRecord.id;
super.onDeleteEvent();
}
}

View File

@ -0,0 +1,11 @@
/** @odoo-module **/
import { CalendarCommonRenderer } from "@web/views/calendar/calendar_common/calendar_common_renderer";
import { CalendarWithRecurrenceCommonPopover } from "./calendar_with_recurrence_common_popover";
export class CalendarWithRecurrenceCommonRenderer extends CalendarCommonRenderer { }
CalendarWithRecurrenceCommonRenderer.components = {
...CalendarCommonRenderer.components,
Popover: CalendarWithRecurrenceCommonPopover,
};

View File

@ -0,0 +1,46 @@
/** @odoo-module */
import { deserializeDateTime, serializeDateTime } from "@web/core/l10n/dates";
import { CalendarModel } from '@web/views/calendar/calendar_model';
export class CalendarWithRecurrenceModel extends CalendarModel {
async loadRecords(data) {
const rawRecords = await this.fetchRecords(data);
const records = {};
let recordsCounter = 1;
for (const rawRecord of rawRecords) {
records[recordsCounter] = {
...this.normalizeRecord(rawRecord),
id: recordsCounter,
};
recordsCounter++;
if (rawRecord.recurring_maintenance && !rawRecord.done && !rawRecord.archive) {
let { start, end } = data.range;
if (rawRecord.repeat_type == 'until') {
end = luxon.DateTime.min(end, deserializeDateTime(rawRecord.repeat_until)).endOf('day');
}
let date = deserializeDateTime(rawRecord.schedule_date);
date = this._getNextDate(date, rawRecord.repeat_unit + 's', rawRecord.repeat_interval);
let counter = 1;
while (date <= end) {
if (date > start) {
const rawRecordCopy = { ...rawRecord };
rawRecordCopy.display_name = rawRecord.display_name + " (+" + counter + ")";
rawRecordCopy.schedule_date = serializeDateTime(date);
records[recordsCounter] = {
...this.normalizeRecord(rawRecordCopy),
id: recordsCounter,
};
recordsCounter++;
}
date = this._getNextDate(date, rawRecord.repeat_unit + 's', rawRecord.repeat_interval);
counter++;
}
}
}
return records;
}
_getNextDate(date, unit, interval) {
return date.plus({ [unit]: interval });
}
}

View File

@ -0,0 +1,15 @@
/** @odoo-module **/
import { CalendarRenderer } from "@web/views/calendar/calendar_renderer";
import { CalendarWithRecurrenceCommonRenderer } from './calendar_with_recurrence_common_renderer';
import { CalendarWithRecurrenceYearRenderer } from './calendar_with_recurrence_year_renderer';
export class CalendarWithRecurrenceRenderer extends CalendarRenderer { }
CalendarWithRecurrenceRenderer.components = {
...CalendarRenderer.components,
day: CalendarWithRecurrenceCommonRenderer,
week: CalendarWithRecurrenceCommonRenderer,
month: CalendarWithRecurrenceCommonRenderer,
year: CalendarWithRecurrenceYearRenderer,
};

View File

@ -0,0 +1,14 @@
/** @odoo-module */
import { calendarView } from '@web/views/calendar/calendar_view';
import { CalendarWithRecurrenceModel } from './calendar_with_recurrence_model';
import { CalendarWithRecurrenceRenderer } from './calendar_with_recurrence_renderer';
import { registry } from '@web/core/registry';
const CalendarWithRecurrenceView = {
...calendarView,
Model: CalendarWithRecurrenceModel,
Renderer: CalendarWithRecurrenceRenderer,
};
registry.category('views').add('calendar_with_recurrence', CalendarWithRecurrenceView);

View File

@ -0,0 +1,10 @@
/** @odoo-module **/
import { CalendarYearPopover } from "@web/views/calendar/calendar_year/calendar_year_popover";
export class CalendarWithRecurrenceYearPopover extends CalendarYearPopover {
onRecordClick(record) {
record.id = record.rawRecord.id;
super.onRecordClick(record);
}
}

View File

@ -0,0 +1,11 @@
/** @odoo-module **/
import { CalendarWithRecurrenceYearPopover } from "./calendar_with_recurrence_year_popover";
import { CalendarYearRenderer } from "@web/views/calendar/calendar_year/calendar_year_renderer";
export class CalendarWithRecurrenceYearRenderer extends CalendarYearRenderer { }
CalendarWithRecurrenceYearRenderer.components = {
...CalendarYearRenderer.components,
Popover: CalendarWithRecurrenceYearPopover,
};

4
tests/__init__.py Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import test_maintenance
from . import test_maintenance_multicompany

80
tests/test_maintenance.py Normal file
View File

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from odoo.tests.common import TransactionCase
class TestEquipment(TransactionCase):
""" Test used to check that when doing equipment/maintenance_request/equipment_category creation."""
def setUp(self):
super(TestEquipment, self).setUp()
self.equipment = self.env['maintenance.equipment']
self.maintenance_request = self.env['maintenance.request']
self.res_users = self.env['res.users']
self.maintenance_team = self.env['maintenance.team']
self.main_company = self.env.ref('base.main_company')
res_user = self.env.ref('base.group_user')
res_manager = self.env.ref('maintenance.group_equipment_manager')
self.user = self.res_users.create(dict(
name="Normal User/Employee",
company_id=self.main_company.id,
login="emp",
email="empuser@yourcompany.example.com",
groups_id=[(6, 0, [res_user.id])]
))
self.manager = self.res_users.create(dict(
name="Equipment Manager",
company_id=self.main_company.id,
login="hm",
email="eqmanager@yourcompany.example.com",
groups_id=[(6, 0, [res_manager.id])]
))
self.equipment_monitor = self.env['maintenance.equipment.category'].create({
'name': 'Monitors - Test',
})
def test_10_equipment_request_category(self):
# Create a new equipment
equipment_01 = self.equipment.with_user(self.manager).create({
'name': 'Samsung Monitor "15',
'category_id': self.equipment_monitor.id,
'technician_user_id': self.ref('base.user_root'),
'owner_user_id': self.user.id,
'assign_date': time.strftime('%Y-%m-%d'),
'serial_no': 'MT/127/18291015',
'model': 'NP355E5X',
'color': 3,
})
# Check that equipment is created or not
assert equipment_01, "Equipment not created"
# Create new maintenance request
maintenance_request_01 = self.maintenance_request.with_user(self.user).create({
'name': 'Resolution is bad',
'user_id': self.user.id,
'owner_user_id': self.user.id,
'equipment_id': equipment_01.id,
'color': 7,
'stage_id': self.ref('maintenance.stage_0'),
'maintenance_team_id': self.ref('maintenance.equipment_team_maintenance')
})
# I check that maintenance_request is created or not
assert maintenance_request_01, "Maintenance Request not created"
# I check that Initially maintenance request is in the "New Request" stage
self.assertEqual(maintenance_request_01.stage_id.id, self.ref('maintenance.stage_0'))
# I check that change the maintenance_request stage on click statusbar
maintenance_request_01.with_user(self.user).write({'stage_id': self.ref('maintenance.stage_1')})
# I check that maintenance request is in the "In Progress" stage
self.assertEqual(maintenance_request_01.stage_id.id, self.ref('maintenance.stage_1'))

View File

@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from odoo.tests.common import TransactionCase
from odoo.exceptions import AccessError
class TestEquipmentMulticompany(TransactionCase):
def test_00_equipment_multicompany_user(self):
"""Test Check maintenance with equipment manager and user in multi company environment"""
# Use full models
Equipment = self.env['maintenance.equipment']
MaintenanceRequest = self.env['maintenance.request']
Category = self.env['maintenance.equipment.category']
ResUsers = self.env['res.users']
ResCompany = self.env['res.company']
MaintenanceTeam = self.env['maintenance.team']
# Use full reference.
group_user = self.env.ref('base.group_user')
group_manager = self.env.ref('maintenance.group_equipment_manager')
# Company A
company_a = ResCompany.create({
'name': 'Company A',
'currency_id': self.env.ref('base.USD').id,
})
# Create one child company having parent company is 'Your company'
company_b = ResCompany.create({
'name': 'Company B',
'currency_id': self.env.ref('base.USD').id,
})
# Create equipment manager.
cids = [company_a.id, company_b.id]
equipment_manager = ResUsers.create({
'name': 'Equipment Manager',
'company_id': company_a.id,
'login': 'e_equipment_manager',
'email': 'eqmanager@yourcompany.example.com',
'groups_id': [(6, 0, [group_manager.id])],
'company_ids': [(6, 0, [company_a.id, company_b.id])]
})
# Create equipment user
user = ResUsers.create({
'name': 'Normal User/Employee',
'company_id': company_b.id,
'login': 'emp',
'email': 'empuser@yourcompany.example.com',
'groups_id': [(6, 0, [group_user.id])],
'company_ids': [(6, 0, [company_b.id])]
})
# create a maintenance team for company A user
team = MaintenanceTeam.with_user(equipment_manager).create({
'name': 'Metrology',
'company_id': company_a.id,
})
# create a maintenance team for company B user
teamb = MaintenanceTeam.with_user(equipment_manager).with_context(allowed_company_ids=cids).create({
'name': 'Subcontractor',
'company_id': company_b.id,
})
# User should not able to create equipment category.
with self.assertRaises(AccessError):
Category.with_user(user).create({
'name': 'Software',
'company_id': company_b.id,
'technician_user_id': user.id,
})
# create equipment category for equipment manager
category_1 = Category.with_user(equipment_manager).with_context(allowed_company_ids=cids).create({
'name': 'Monitors - Test',
'company_id': company_b.id,
'technician_user_id': equipment_manager.id,
})
# create equipment category for equipment manager
Category.with_user(equipment_manager).with_context(allowed_company_ids=cids).create({
'name': 'Computers - Test',
'company_id': company_b.id,
'technician_user_id': equipment_manager.id,
})
# create equipment category for equipment user
Category.with_user(equipment_manager).create({
'name': 'Phones - Test',
'company_id': company_a.id,
'technician_user_id': equipment_manager.id,
})
# Check category for user equipment_manager and user
self.assertEqual(Category.with_user(equipment_manager).with_context(allowed_company_ids=cids).search_count([]), 3)
self.assertEqual(Category.with_user(user).search_count([]), 2)
# User should not able to create equipment.
with self.assertRaises(AccessError):
Equipment.with_user(user).create({
'name': 'Samsung Monitor 15',
'category_id': category_1.id,
'assign_date': time.strftime('%Y-%m-%d'),
'company_id': company_b.id,
'owner_user_id': user.id,
})
Equipment.with_user(equipment_manager).with_context(allowed_company_ids=cids).create({
'name': 'Acer Laptop',
'category_id': category_1.id,
'assign_date': time.strftime('%Y-%m-%d'),
'company_id': company_b.id,
'owner_user_id': user.id,
})
# create an equipment for user
Equipment.with_user(equipment_manager).with_context(allowed_company_ids=cids).create({
'name': 'HP Laptop',
'category_id': category_1.id,
'assign_date': time.strftime('%Y-%m-%d'),
'company_id': company_b.id,
'owner_user_id': equipment_manager.id,
})
# Now there are total 2 equipment created and can view by equipment_manager user
self.assertEqual(Equipment.with_user(equipment_manager).with_context(allowed_company_ids=cids).search_count([]), 2)
# And there is total 1 equipment can be view by Normal User ( Which user is followers)
self.assertEqual(Equipment.with_user(user).search_count([]), 1)
# create an equipment team BY user
with self.assertRaises(AccessError):
MaintenanceTeam.with_user(user).create({
'name': 'Subcontractor',
'company_id': company_b.id,
})
# create an equipment category BY user
with self.assertRaises(AccessError):
Category.with_user(user).create({
'name': 'Computers',
'company_id': company_b.id,
'technician_user_id': user.id,
})
# create an maintenance stage BY user
with self.assertRaises(AccessError):
self.env['maintenance.stage'].with_user(user).create({
'name': 'identify corrective maintenance requirements',
})
# Create an maintenance request for ( User Follower ).
MaintenanceRequest.with_user(user).create({
'name': 'Some keys are not working',
'company_id': company_b.id,
'user_id': user.id,
'owner_user_id': user.id,
})
# Create an maintenance request for equipment_manager (Admin Follower)
MaintenanceRequest.with_user(equipment_manager).create({
'name': 'Battery drains fast',
'company_id': company_a.id,
'user_id': equipment_manager.id,
'owner_user_id': equipment_manager.id,
})
# Now here is total 1 maintenance request can be view by Normal User
self.assertEqual(MaintenanceRequest.with_user(equipment_manager).with_context(allowed_company_ids=cids).search_count([]), 2)
self.assertEqual(MaintenanceRequest.with_user(user).search_count([]), 1)

View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<odoo>
<!-- Activity types config -->
<record id="mail_activity_type_action_config_maintenance" model="ir.actions.act_window">
<field name="name">Activity Types</field>
<field name="res_model">mail.activity.type</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('res_model', '=', False), ('res_model', '=', 'maintenance.request')]</field>
<field name="context">{'default_res_model': 'maintenance.request'}</field>
</record>
<menuitem id="maintenance_menu_config_activity_type"
action="mail_activity_type_action_config_maintenance"
parent="menu_maintenance_configuration"
sequence="20"
groups="base.group_no_one"/>
</odoo>

1136
views/maintenance_views.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.maintenance</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="35"/>
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app data-string="Maintenance" string="Maintenance" name="maintenance" groups="maintenance.group_equipment_manager">
<block title="Custom Worksheets">
<setting help="Create custom worksheet templates">
<field name="module_maintenance_worksheet"/>
</setting>
</block>
</app>
</xpath>
</field>
</record>
<record id="action_maintenance_configuration" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module' : 'maintenance', 'bin_size': False}</field>
</record>
<menuitem id="menu_maintenance_config" name="Settings" parent="menu_maintenance_configuration"
sequence="0" action="action_maintenance_configuration" groups="base.group_system"/>
</data>
</odoo>