mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Merge branch 'mig/12.0/maintenance_repair' into '12.0-test'
TEST: mig/12.0/maintenance_repair See merge request hibou-io/hibou-odoo/suite!46
This commit is contained in:
33
maintenance_repair/README.rst
Normal file
33
maintenance_repair/README.rst
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
**************************
|
||||||
|
Hibou - Maintenance Repair
|
||||||
|
**************************
|
||||||
|
|
||||||
|
Keep track of parts required to repair equipment.
|
||||||
|
|
||||||
|
For more information and add-ons, visit `Hibou.io <https://hibou.io/>`_.
|
||||||
|
|
||||||
|
|
||||||
|
=============
|
||||||
|
Main Features
|
||||||
|
=============
|
||||||
|
|
||||||
|
* Consume products on Maintenance Requests.
|
||||||
|
* New Model: Maintenance Request Repair Line
|
||||||
|
* New 'Parts' notebook tab on Maintenance Request form.
|
||||||
|
* New Equipment Repair filter 'To Repair' to view maintenance requests with part line items that have not yet been consumed.
|
||||||
|
* Tally for total cost of Parts.
|
||||||
|
* Includes Employee permissions for managing maintenance request repair line items
|
||||||
|
|
||||||
|
.. image:: https://user-images.githubusercontent.com/15882954/41262389-6665024a-6d95-11e8-9d94-236c635e1cf2.png
|
||||||
|
:alt: 'Equipment Request Detail'
|
||||||
|
:width: 988
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
|
||||||
|
=======
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
Please see `LICENSE <https://github.com/hibou-io/hibou-odoo-suite/blob/11.0/LICENSE>`_.
|
||||||
|
|
||||||
|
Copyright Hibou Corp. 2018
|
||||||
1
maintenance_repair/__init__.py
Normal file
1
maintenance_repair/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
25
maintenance_repair/__manifest__.py
Normal file
25
maintenance_repair/__manifest__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
'name': 'Equipment Repair',
|
||||||
|
'version': '12.0.1.0.0',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'summary': 'Consume products on Maintenance Requests',
|
||||||
|
'description': """
|
||||||
|
Equipment Repair
|
||||||
|
================
|
||||||
|
|
||||||
|
Keep track of parts required to repair equipment.
|
||||||
|
""",
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'depends': [
|
||||||
|
'stock',
|
||||||
|
'maintenance_notebook',
|
||||||
|
'hr_department_project',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/maintenance_views.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
||||||
1
maintenance_repair/models/__init__.py
Normal file
1
maintenance_repair/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import maintenance
|
||||||
142
maintenance_repair/models/maintenance.py
Normal file
142
maintenance_repair/models/maintenance.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.addons import decimal_precision as dp
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class StockMove(models.Model):
|
||||||
|
_inherit = 'stock.move'
|
||||||
|
|
||||||
|
maintenance_request_id = fields.Many2one('maintenance.request')
|
||||||
|
|
||||||
|
|
||||||
|
class MaintenanceTeam(models.Model):
|
||||||
|
_inherit = 'maintenance.team'
|
||||||
|
|
||||||
|
repair_location_id = fields.Many2one('stock.location', string='Default Repair Parts Source')
|
||||||
|
repair_location_dest_id = fields.Many2one('stock.location', string='Default Repair Parts Destination')
|
||||||
|
|
||||||
|
|
||||||
|
class MaintenanceRequest(models.Model):
|
||||||
|
_inherit = 'maintenance.request'
|
||||||
|
|
||||||
|
maintenance_type = fields.Selection(selection_add=[('negligence', 'Negligence')])
|
||||||
|
repair_line_ids = fields.One2many('maintenance.request.repair.line', 'request_id', 'Parts', copy=True)
|
||||||
|
repair_status = fields.Selection([
|
||||||
|
('repaired', 'Repaired'),
|
||||||
|
('to repair', 'To Repair'),
|
||||||
|
('no', 'Nothing to Repair')
|
||||||
|
], string='Repair Status', compute='_get_repaired', store=True, readonly=True)
|
||||||
|
repair_location_id = fields.Many2one('stock.location', string='Source Location')
|
||||||
|
repair_location_dest_id = fields.Many2one('stock.location', string='Destination Location')
|
||||||
|
total_lst_price = fields.Float(string='Total Price', compute='_compute_repair_totals', stored=True)
|
||||||
|
total_standard_price = fields.Float(string='Total Est. Cost', compute='_compute_repair_totals', stored=True)
|
||||||
|
total_cost = fields.Float(string='Total Cost', compute='_compute_repair_totals', stored=True)
|
||||||
|
|
||||||
|
@api.depends('repair_line_ids.lst_price', 'repair_line_ids.standard_price', 'repair_line_ids.cost')
|
||||||
|
def _compute_repair_totals(self):
|
||||||
|
for repair in self:
|
||||||
|
repair.total_lst_price = sum(l.lst_price for l in repair.repair_line_ids)
|
||||||
|
repair.total_standard_price = sum(l.standard_price for l in repair.repair_line_ids)
|
||||||
|
repair.total_cost = sum(l.cost for l in repair.repair_line_ids)
|
||||||
|
|
||||||
|
@api.depends('repair_line_ids.state')
|
||||||
|
def _get_repaired(self):
|
||||||
|
for request in self:
|
||||||
|
if not request.repair_line_ids:
|
||||||
|
request.repair_status = 'no'
|
||||||
|
elif request.repair_line_ids.filtered(lambda l: l.state != 'done'):
|
||||||
|
request.repair_status = 'to repair'
|
||||||
|
else:
|
||||||
|
request.repair_status = 'repaired'
|
||||||
|
|
||||||
|
@api.onchange('maintenance_team_id')
|
||||||
|
def _onchange_maintenance_team(self):
|
||||||
|
for request in self:
|
||||||
|
if request.maintenance_team_id:
|
||||||
|
request.repair_location_id = request.maintenance_team_id.repair_location_id
|
||||||
|
request.repair_location_dest_id = request.maintenance_team_id.repair_location_dest_id
|
||||||
|
|
||||||
|
|
||||||
|
def action_complete_repair(self):
|
||||||
|
for request in self.filtered(lambda r: r.repair_status == 'to repair'):
|
||||||
|
request.repair_line_ids.action_complete()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class MaintenanceRequestRepairLine(models.Model):
|
||||||
|
_name = 'maintenance.request.repair.line'
|
||||||
|
|
||||||
|
request_id = fields.Many2one('maintenance.request', copy=False)
|
||||||
|
product_id = fields.Many2one('product.product', 'Product', required=True,
|
||||||
|
states={'done': [('readonly', True)]})
|
||||||
|
product_uom_qty = fields.Float('Quantity', default=1.0,
|
||||||
|
digits=dp.get_precision('Product Unit of Measure'), required=True,
|
||||||
|
states={'done': [('readonly', True)]})
|
||||||
|
product_uom_id = fields.Many2one('uom.uom', 'Product Unit of Measure', required=True,
|
||||||
|
states={'done': [('readonly', True)]})
|
||||||
|
state = fields.Selection([
|
||||||
|
('draft', 'Draft'),
|
||||||
|
('done', 'Done'),
|
||||||
|
], string='State', copy=False, default='draft')
|
||||||
|
move_id = fields.Many2one('stock.move', string='Stock Move')
|
||||||
|
lst_price = fields.Float(string='Sale Price', states={'done': [('readonly', True)]})
|
||||||
|
standard_price = fields.Float(string='Est. Cost', states={'done': [('readonly', True)]})
|
||||||
|
cost = fields.Float(string='Cost', compute='_compute_actual_cost', stored=True)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def unlink(self):
|
||||||
|
if self.filtered(lambda l: l.state == 'done'):
|
||||||
|
raise UserError(_('Only draft lines can be deleted.'))
|
||||||
|
return super(MaintenanceRequestRepairLine, self).unlink()
|
||||||
|
|
||||||
|
@api.onchange('product_id', 'product_uom_qty')
|
||||||
|
def onchange_product_id(self):
|
||||||
|
if self.product_id:
|
||||||
|
self.product_uom_id = self.product_id.uom_id
|
||||||
|
self.lst_price = self.product_id.lst_price * self.product_uom_qty
|
||||||
|
self.standard_price = self.product_id.standard_price * self.product_uom_qty
|
||||||
|
|
||||||
|
@api.depends('product_id', 'move_id')
|
||||||
|
def _compute_actual_cost(self):
|
||||||
|
for line in self:
|
||||||
|
if line.move_id:
|
||||||
|
line.cost = sum(abs(m.amount) for m in line.move_id.account_move_ids)
|
||||||
|
else:
|
||||||
|
line.cost = 0.0
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_complete(self):
|
||||||
|
# Create stock movements. - Inspired by mrp_repair
|
||||||
|
MoveObj = self.env['stock.move']
|
||||||
|
for line in self.filtered(lambda l: not l.state == 'done'):
|
||||||
|
request = line.request_id
|
||||||
|
|
||||||
|
# Optional hooks to `maintenance_timesheet` and `stock_analytic`
|
||||||
|
analytic_account_id = False
|
||||||
|
if getattr(request, 'project_id', False):
|
||||||
|
analytic_account_id = request.project_id.analytic_account_id.id
|
||||||
|
|
||||||
|
move = MoveObj.create({
|
||||||
|
'name': request.name,
|
||||||
|
'product_id': line.product_id.id,
|
||||||
|
'product_uom_qty': line.product_uom_qty,
|
||||||
|
'product_uom': line.product_uom_id.id,
|
||||||
|
'location_id': request.repair_location_id.id,
|
||||||
|
'location_dest_id': request.repair_location_dest_id.id,
|
||||||
|
'maintenance_request_id': request.id,
|
||||||
|
'origin': request.name,
|
||||||
|
'analytic_account_id': analytic_account_id,
|
||||||
|
})
|
||||||
|
move = move.sudo()
|
||||||
|
move._action_confirm()
|
||||||
|
move._action_assign()
|
||||||
|
if move.state != 'assigned':
|
||||||
|
raise ValidationError(_('Unable to reserve inventory.'))
|
||||||
|
|
||||||
|
move.quantity_done = line.product_uom_qty
|
||||||
|
move._action_done()
|
||||||
|
if move.state != 'done':
|
||||||
|
raise ValidationError(_('Unable to move inventory.'))
|
||||||
|
|
||||||
|
line.write({'move_id': move.id, 'state': 'done'})
|
||||||
|
return True
|
||||||
2
maintenance_repair/security/ir.model.access.csv
Normal file
2
maintenance_repair/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||||
|
"access_maintenance_request_repair_line","access maintenance.request.repair.line","model_maintenance_request_repair_line","base.group_user",1,1,1,1
|
||||||
|
1
maintenance_repair/tests/__init__.py
Normal file
1
maintenance_repair/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import test_maintenance_repair
|
||||||
37
maintenance_repair/tests/test_maintenance_repair.py
Normal file
37
maintenance_repair/tests/test_maintenance_repair.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from odoo.tests import common
|
||||||
|
|
||||||
|
|
||||||
|
class TestMaintenanceRepair(common.TransactionCase):
|
||||||
|
"""Tests for repairs
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
equipment = self.env['maintenance.equipment'].create({
|
||||||
|
'name': 'Monitor',
|
||||||
|
})
|
||||||
|
|
||||||
|
loc_from = self.env.ref('stock.stock_location_stock')
|
||||||
|
loc_to = self.env.ref('stock.location_inventory')
|
||||||
|
request = self.env['maintenance.request'].create({
|
||||||
|
'name': 'Repair Monitor',
|
||||||
|
'equipment_id': equipment.id,
|
||||||
|
'repair_location_id': loc_from.id,
|
||||||
|
'repair_location_dest_id': loc_to.id,
|
||||||
|
})
|
||||||
|
self.assertEqual(request.repair_status, 'no')
|
||||||
|
|
||||||
|
product_to_repair = self.env.ref('product.product_product_24_product_template')
|
||||||
|
line = self.env['maintenance.request.repair.line'].create({
|
||||||
|
'request_id': request.id,
|
||||||
|
'product_id': product_to_repair.id,
|
||||||
|
'product_uom_id': product_to_repair.uom_id.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(request.repair_status, 'to repair')
|
||||||
|
line.action_complete()
|
||||||
|
self.assertEqual(request.repair_status, 'repaired')
|
||||||
|
self.assertEqual(line.state, 'done')
|
||||||
|
self.assertTrue(line.move_id, 'Expect a stock move to be done.')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
81
maintenance_repair/views/maintenance_views.xml
Normal file
81
maintenance_repair/views/maintenance_views.xml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record model="ir.ui.view" id="maintenance_team_view_form_inherited">
|
||||||
|
<field name="name">maintenance.team.form.inherited</field>
|
||||||
|
<field name="model">maintenance.team</field>
|
||||||
|
<field name="inherit_id" ref="maintenance.maintenance_team_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='member_ids']" position="after">
|
||||||
|
<field name="repair_location_id"/>
|
||||||
|
<field name="repair_location_dest_id"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_equipment_request_view_form_inherited">
|
||||||
|
<field name="name">equipment.request.form.inherited</field>
|
||||||
|
<field name="model">maintenance.request</field>
|
||||||
|
<field name="inherit_id" ref="maintenance.hr_equipment_request_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//button[@name='archive_equipment_request']" position="before">
|
||||||
|
<field name="repair_status" invisible="1"/>
|
||||||
|
<button type="object" name="action_complete_repair" string="Complete Repair" class="oe_highlight"
|
||||||
|
attrs="{'invisible': [('repair_status', '!=', 'to repair')]}"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_equipment_request_view_form_notebook_inherited">
|
||||||
|
<field name="name">equipment.request.form.notebook.inherited</field>
|
||||||
|
<field name="model">maintenance.request</field>
|
||||||
|
<field name="inherit_id" ref="maintenance_notebook.hr_equipment_request_view_form_notebook"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//notebook" position="inside">
|
||||||
|
<page string="Parts">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="repair_location_id"/>
|
||||||
|
<field name="repair_status" invisible="1"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="repair_location_dest_id"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<field name="repair_line_ids">
|
||||||
|
<tree editable="bottom" string="Part Operations" colors="blue:state=='draft'">
|
||||||
|
<field name="state" invisible="1"/>
|
||||||
|
<field name="product_id" domain="[('type', 'in', ['product', 'consu'])]"/>
|
||||||
|
<field name="product_uom_id"/>
|
||||||
|
<field name="product_uom_qty"/>
|
||||||
|
<field name="lst_price"/>
|
||||||
|
<field name="standard_price" groups="account.group_account_user"/>
|
||||||
|
<field name="cost" groups="account.group_account_user"/>
|
||||||
|
<button type="object" name="action_complete" string="Complete" class="oe_highlight"
|
||||||
|
attrs="{'invisible': [('state', '=', 'done')]}"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<group class="oe_subtotal_footer oe_right" name="repair_totals">
|
||||||
|
<field name="total_lst_price" />
|
||||||
|
<field name="total_standard_price" groups="account.group_account_user"/>
|
||||||
|
<field name="total_cost" groups="account.group_account_user"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_equipment_request_view_search_inherited">
|
||||||
|
<field name="name">equipment.request.search.inherited</field>
|
||||||
|
<field name="model">maintenance.request</field>
|
||||||
|
<field name="inherit_id" ref="maintenance.hr_equipment_request_view_search"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//filter[@name='top_priority']" position="after">
|
||||||
|
<filter string="To Repair" name="to_repair" domain="[('repair_status', '=', 'to repair')]"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user