mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[9.0][ADD] mrp_production_request
This commit is contained in:
95
mrp_production_request/README.rst
Normal file
95
mrp_production_request/README.rst
Normal file
@@ -0,0 +1,95 @@
|
||||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
|
||||
======================
|
||||
MRP Production Request
|
||||
======================
|
||||
|
||||
This module extends the functionality of Manufacturing to allow you to use
|
||||
Manufacturing Request (MR) as a previous step to Manufacturing Orders (MO).
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
To configure this module to automatically generate Manufacturing Requests
|
||||
from procurement orders instead of directly create manufacturing orders yo
|
||||
need to:
|
||||
|
||||
#. Go to the products that you want them to trigger manufacturing orders.
|
||||
#. Go to the *Inventory* tab.
|
||||
#. Check the route *manufacture* and the box *Manufacturing Request*.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
To use this module, you need to:
|
||||
|
||||
#. Go to *Manufacturing > Manufacturing Requests*.
|
||||
#. Create a manufacturing request or open a existing one (assigned to you or
|
||||
created from a procurement).
|
||||
#. If you click on *Request approval* button the user assigned as approver
|
||||
will added to the thread.
|
||||
#. If you are the approver you can either click on *Approve* or *Reject*
|
||||
buttons.
|
||||
#. Rejecting a MR will cancel associated procurements and propagate this
|
||||
cancellation.
|
||||
#. Approving a MR will allow to create manufacturing orders.
|
||||
#. You can manually set to done a request by clicking in the button *Done*.
|
||||
|
||||
To create MOs from MRs you have to:
|
||||
|
||||
#. Go to approved manufacturing request.
|
||||
#. Click on the button *Create Manufacturing Order*.
|
||||
#. In the opened wizard, click on *Compute lines* so you will have a
|
||||
quantity proposed for creating a MO. This quantity is the maximum quantity
|
||||
you can produce with the current stock available in the source location.
|
||||
#. Use the proposed quantity or change it and click on *Create MO* at the
|
||||
bottom of the wizard.
|
||||
|
||||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||
:alt: Try me on Runbot
|
||||
:target: https://runbot.odoo-community.org/runbot/129/9.0
|
||||
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
* Take into account workstations.
|
||||
* Take into account consumable products.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/manufacture/issues>`_. In case of trouble, please
|
||||
check there if your issue has already been reported. If you spotted it first,
|
||||
help us smash it by providing detailed and welcomed feedback.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Lois Rilo Antelo <lois.rilo@eficent.com>
|
||||
* Jordi Ballester <jordi.ballester@eficent.com>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
To contribute to this module, please visit https://odoo-community.org.
|
||||
6
mrp_production_request/__init__.py
Normal file
6
mrp_production_request/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import models
|
||||
from . import wizards
|
||||
28
mrp_production_request/__openerp__.py
Normal file
28
mrp_production_request/__openerp__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
{
|
||||
"name": "MRP Request",
|
||||
"summary": "Allows you to use Manufacturing Request as a previous "
|
||||
"step to Manufacturing Orders for better manufacture "
|
||||
"planification.",
|
||||
"version": "9.0.1.0.0",
|
||||
"category": "Manufacturing",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"author": "Eficent,"
|
||||
"Odoo Community Association (OCA)",
|
||||
"license": "AGPL-3",
|
||||
"application": False,
|
||||
"installable": True,
|
||||
"depends": ["mrp", "stock_available_unreserved"],
|
||||
"data": [
|
||||
"security/mrp_production_request_security.xml",
|
||||
"security/ir.model.access.csv",
|
||||
"data/mrp_production_request_sequence.xml",
|
||||
"wizards/mrp_production_request_create_mo_view.xml",
|
||||
"views/mrp_production_request_view.xml",
|
||||
"views/product_template_view.xml",
|
||||
"views/procurement_order_view.xml",
|
||||
"views/mrp_production_view.xml",
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="seq_mrp_production_request" model="ir.sequence">
|
||||
<field name="name">Manufacturing Request</field>
|
||||
<field name="code">mrp.production.request</field>
|
||||
<field name="prefix">MR/%(range_year)s/</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
9
mrp_production_request/models/__init__.py
Normal file
9
mrp_production_request/models/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import mrp_production_request
|
||||
from . import mrp_production
|
||||
from . import procurement
|
||||
from . import product
|
||||
from . import stock_move
|
||||
13
mrp_production_request/models/mrp_production.py
Normal file
13
mrp_production_request/models/mrp_production.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import fields, models
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = "mrp.production"
|
||||
|
||||
mrp_production_request_id = fields.Many2one(
|
||||
comodel_name="mrp.production.request",
|
||||
string="Manufacturing Request", copy=False, readonly=True)
|
||||
202
mrp_production_request/models/mrp_production_request.py
Normal file
202
mrp_production_request/models/mrp_production_request.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import api, fields, models, _
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
|
||||
class MrpProductionRequest(models.Model):
|
||||
_name = "mrp.production.request"
|
||||
_description = "Manufacturing Request"
|
||||
_inherit = "mail.thread"
|
||||
|
||||
@api.model
|
||||
def _company_get(self):
|
||||
company_id = self.env['res.company']._company_default_get(self._name)
|
||||
return self.env['res.company'].browse(company_id.id)
|
||||
|
||||
@api.model
|
||||
def _get_default_requested_by(self):
|
||||
return self.env.user
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
"""Add sequence if name is not defined and subscribe to the thread
|
||||
the user assigned to the request."""
|
||||
if not vals.get('name') or vals.get('name') == '/':
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code(
|
||||
'mrp.production.request') or '/'
|
||||
res = super(MrpProductionRequest, self).create(vals)
|
||||
if vals.get('assigned_to'):
|
||||
res.message_subscribe_users(user_ids=[res.assigned_to.id])
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
res = super(MrpProductionRequest, self).write(vals)
|
||||
for request in self:
|
||||
if vals.get('assigned_to'):
|
||||
self.message_subscribe_users(user_ids=[request.assigned_to.id])
|
||||
return res
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_uom(self):
|
||||
self.product_uom = self.product_id.uom_id
|
||||
self.bom_id = self.env['mrp.bom']._bom_find(
|
||||
product_id=self.product_id.id, properties=[])
|
||||
|
||||
@api.model
|
||||
def _get_mo_valid_states(self):
|
||||
return ['draft', 'confirmed', 'ready', 'in_production', 'done']
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_production_ids', 'mrp_production_ids.state', 'state')
|
||||
def _compute_manufactured_qty(self):
|
||||
valid_states = self._get_mo_valid_states()
|
||||
valid_mo = self.mrp_production_ids.filtered(
|
||||
lambda mo: mo.state in valid_states).mapped('product_qty')
|
||||
self.manufactured_qty = sum(valid_mo)
|
||||
self.pending_qty = max(self.product_qty - self.manufactured_qty, 0.0)
|
||||
|
||||
name = fields.Char(
|
||||
default="/", required=True,
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
origin = fields.Char(
|
||||
string='Source Document',
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
requested_by = fields.Many2one(
|
||||
comodel_name='res.users', string='Requested by',
|
||||
default=_get_default_requested_by,
|
||||
required=True, track_visibility='onchange',
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
assigned_to = fields.Many2one(
|
||||
comodel_name='res.users', string='Approver',
|
||||
track_visibility='onchange',
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
description = fields.Text('Description')
|
||||
date_planned = fields.Date(
|
||||
string='Planned Date', required=True, track_visibility='onchange',
|
||||
default=fields.Date.context_today)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company', string='Company',
|
||||
required=True, default=_company_get)
|
||||
mrp_production_ids = fields.One2many(
|
||||
comodel_name="mrp.production", string="Manufacturing Orders",
|
||||
inverse_name="mrp_production_request_id")
|
||||
state = fields.Selection(
|
||||
selection=[("draft", "Draft"),
|
||||
("to_approve", "To Be Approved"),
|
||||
("approved", "Approved"),
|
||||
("done", "Done"),
|
||||
("cancel", "Cancelled")],
|
||||
index=True, track_visibility='onchange',
|
||||
required=True, copy=False, default='draft')
|
||||
procurement_id = fields.Many2one(
|
||||
comodel_name="procurement.order", string="Procurement Order",
|
||||
readonly=True)
|
||||
procurement_state = fields.Selection(
|
||||
related='procurement_id.state',
|
||||
store=True, readonly=True, string="Procurement State")
|
||||
product_id = fields.Many2one(
|
||||
comodel_name="product.product", string="Product", required=True,
|
||||
domain=[('type', 'in', ['product', 'consu'])],
|
||||
track_visibility="onchange",
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
product_tmpl_id = fields.Many2one(
|
||||
comodel_name='product.template', string='Product Template',
|
||||
related='product_id.product_tmpl_id')
|
||||
product_qty = fields.Float(
|
||||
required=True, track_visibility='onchange',
|
||||
digits_compute=dp.get_precision('Product Unit of Measure'),
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
product_uom = fields.Many2one(
|
||||
comodel_name='product.uom', string='Unit of Measure',
|
||||
readonly=True, states={'draft': [('readonly', False)]},
|
||||
domain="[('category_id', '=', category_uom_id)]")
|
||||
category_uom_id = fields.Many2one(related="product_uom.category_id")
|
||||
manufactured_qty = fields.Float(
|
||||
string="Quantity in Manufacturing Orders",
|
||||
compute=_compute_manufactured_qty, store=True,
|
||||
digits_compute=dp.get_precision('Product Unit of Measure'),
|
||||
readonly=True)
|
||||
pending_qty = fields.Float(
|
||||
string="Pending Quantity", compute=_compute_manufactured_qty,
|
||||
store=True, digits_compute=dp.get_precision('Product Unit of Measure'),
|
||||
readonly=True)
|
||||
bom_id = fields.Many2one(
|
||||
comodel_name="mrp.bom", string="Bill of Materials", required=True,
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
routing_id = fields.Many2one(
|
||||
comodel_name='mrp.routing', string='Routing',
|
||||
on_delete='setnull', readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
help="The list of operations (list of work centers) to produce "
|
||||
"the finished product. The routing is mainly used to compute "
|
||||
"work center costs during operations and to plan future loads "
|
||||
"on work centers based on production plannification.")
|
||||
location_src_id = fields.Many2one(
|
||||
comodel_name='stock.location', string='Raw Materials Location',
|
||||
default=lambda self: self.env['stock.location'].browse(
|
||||
self.env['mrp.production']._src_id_default()),
|
||||
required=True, readonly=True, states={'draft': [('readonly', False)]})
|
||||
location_dest_id = fields.Many2one(
|
||||
comodel_name='stock.location', string='Finished Products Location',
|
||||
default=lambda self: self.env['stock.location'].browse(
|
||||
self.env['mrp.production']._dest_id_default()),
|
||||
required=True, readonly=True, states={'draft': [('readonly', False)]})
|
||||
|
||||
@api.multi
|
||||
def button_to_approve(self):
|
||||
self.write({'state': 'to_approve'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def button_approved(self):
|
||||
self.write({'state': 'approved'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def button_done(self):
|
||||
self.write({'state': 'done'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def _check_reset_allowed(self):
|
||||
if any([s in ['done', 'cancel'] for s in self.mapped(
|
||||
'procurement_id.state')]):
|
||||
raise UserError(
|
||||
_('You cannot reset a manufacturing request related to '
|
||||
'done or cancelled procurement orders.'))
|
||||
|
||||
@api.multi
|
||||
def button_draft(self):
|
||||
self._check_reset_allowed()
|
||||
self.write({'state': 'draft'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def _check_cancel_allowed(self):
|
||||
if any([s == 'done' for s in self.mapped(
|
||||
'procurement_id.state')]):
|
||||
raise UserError(
|
||||
_('You cannot reject a manufacturing request related to '
|
||||
'done procurement orders.'))
|
||||
|
||||
@api.multi
|
||||
def button_cancel(self):
|
||||
self._check_cancel_allowed()
|
||||
self.write({'state': 'cancel'})
|
||||
self.mapped('procurement_id').with_context(
|
||||
from_mrp_production_request=True).cancel()
|
||||
if not self.env.context.get('cancel_procurement'):
|
||||
procurements = self.mapped('procurement_id')
|
||||
procurements.filtered(lambda r: r.state not in (
|
||||
'cancel', 'exception') and not r.rule_id.propagate).write(
|
||||
{'state': 'exception'})
|
||||
moves = procurements.filtered(
|
||||
lambda r: r.rule_id.propagate).mapped(
|
||||
'move_dest_id')
|
||||
moves.filtered(lambda r: r.state != 'cancel').action_cancel()
|
||||
return True
|
||||
51
mrp_production_request/models/procurement.py
Normal file
51
mrp_production_request/models/procurement.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import api, fields, models, _
|
||||
|
||||
|
||||
class ProcurementOrder(models.Model):
|
||||
_inherit = "procurement.order"
|
||||
|
||||
mrp_production_request_id = fields.Many2one(
|
||||
comodel_name="mrp.production.request", string="Manufacturing Request",
|
||||
copy=False)
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_production_request(self, procurement):
|
||||
data = self._prepare_mo_vals(procurement)
|
||||
data['procurement_id'] = procurement.id
|
||||
data['state'] = 'to_approve'
|
||||
return data
|
||||
|
||||
@api.model
|
||||
def _run(self, procurement):
|
||||
if (procurement.rule_id and
|
||||
procurement.rule_id.action == 'manufacture' and
|
||||
procurement.product_id.mrp_production_request):
|
||||
if not self.mrp_production_request_id:
|
||||
request_data = self._prepare_mrp_production_request(
|
||||
procurement)
|
||||
req = self.env['mrp.production.request'].create(request_data)
|
||||
procurement.message_post(body=_(
|
||||
"Manufacturing Request created"))
|
||||
procurement.mrp_production_request_id = req.id
|
||||
return True
|
||||
return super(ProcurementOrder, self)._run(procurement)
|
||||
|
||||
@api.multi
|
||||
def propagate_cancels(self):
|
||||
result = super(ProcurementOrder, self).propagate_cancels()
|
||||
for procurement in self:
|
||||
mrp_production_requests = \
|
||||
self.env['mrp.production.request'].search([
|
||||
('procurement_id', '=', procurement.id)])
|
||||
if mrp_production_requests and not self.env.context.get(
|
||||
'from_mrp_production_request'):
|
||||
mrp_production_requests.button_cancel()
|
||||
for mr in mrp_production_requests:
|
||||
mr.message_post(
|
||||
body=_("Related procurement has been cancelled."))
|
||||
procurement.write({'mrp_production_request_id': None})
|
||||
return result
|
||||
14
mrp_production_request/models/product.py
Normal file
14
mrp_production_request/models/product.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
mrp_production_request = fields.Boolean(
|
||||
string='Manufacturing Request', help="Check this box to generate "
|
||||
"manufacturing request instead of generating requests manufacturing "
|
||||
"orders from procurement.", default=False)
|
||||
18
mrp_production_request/models/stock_move.py
Normal file
18
mrp_production_request/models/stock_move.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import api, models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if 'production_id' in vals:
|
||||
production = self.env['mrp.production'].browse(
|
||||
vals['production_id'])
|
||||
if production.mrp_production_request_id:
|
||||
vals['propagate'] = False
|
||||
return super(StockMove, self).create(vals)
|
||||
3
mrp_production_request/security/ir.model.access.csv
Normal file
3
mrp_production_request/security/ir.model.access.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mrp_production_request_user,mrp.request.user,model_mrp_production_request,group_mrp_production_request_user,1,1,1,1
|
||||
access_mrp_production_request_manager,mrp.request.manager,model_mrp_production_request,group_mrp_production_request_manager,1,1,1,1
|
||||
|
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
|
||||
<odoo>
|
||||
|
||||
<data noupdate="0">
|
||||
|
||||
<record model="ir.module.category" id="module_category_mrp_production_request">
|
||||
<field name="name">Manufacturing Request</field>
|
||||
<field name="parent_id" ref="base.module_category_manufacturing"/>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
|
||||
<record id="group_mrp_production_request_user" model="res.groups">
|
||||
<field name="name">User</field>
|
||||
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="category_id" ref="module_category_mrp_production_request"/>
|
||||
</record>
|
||||
|
||||
<record id="group_mrp_production_request_manager" model="res.groups">
|
||||
<field name="name">Manager</field>
|
||||
<field name="implied_ids"
|
||||
eval="[(4, ref('mrp_production_request.group_mrp_production_request_user'))]"/>
|
||||
<field name="users" eval="[(4, ref('base.user_root'))]"/>
|
||||
<field name="category_id" ref="module_category_mrp_production_request"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
<data noupdate="0">
|
||||
|
||||
<record model="ir.rule" id="mrp_production_request_comp_rule">
|
||||
<field name="name">Manufacturing Request multi-company</field>
|
||||
<field name="model_id" ref="model_mrp_production_request"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),
|
||||
('company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_production_request_followers_rule" model="ir.rule">
|
||||
<field name="name">Follow Manufacturing Request</field>
|
||||
<field name="model_id" ref="model_mrp_production_request"/>
|
||||
<field name="groups" eval="[(6,0, [ref('group_mrp_production_request_user')])]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
<field name="domain_force">['|',('requested_by','=',user.id),
|
||||
('message_partner_ids', 'in', [user.partner_id.id])]</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_production_request_rule" model="ir.rule">
|
||||
<field name="name">Manufacturing Request User</field>
|
||||
<field name="model_id" ref="model_mrp_production_request"/>
|
||||
<field name="groups" eval="[(6,0, [ref('group_mrp_production_request_user')])]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
<field name="domain_force">[('requested_by','=',user.id)]</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
BIN
mrp_production_request/static/description/icon.png
Normal file
BIN
mrp_production_request/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
223
mrp_production_request/views/mrp_production_request_view.xml
Normal file
223
mrp_production_request/views/mrp_production_request_view.xml
Normal file
@@ -0,0 +1,223 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_mrp_production_request_form">
|
||||
<field name="name">mrp.production.request.form</field>
|
||||
<field name="model">mrp.production.request</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Manufacturing Request">
|
||||
<header>
|
||||
<button name="button_draft"
|
||||
states="to_approve,approved,cancel,done"
|
||||
string="Reset"
|
||||
type="object"
|
||||
groups="purchase_request.group_purchase_request_manager"/>
|
||||
<button name="button_to_approve" states="draft"
|
||||
string="Request approval" type="object"
|
||||
class="oe_highlight"/>
|
||||
<button name="button_approved" states="to_approve"
|
||||
string="Approve" type="object" class="oe_highlight"
|
||||
groups="mrp_production_request.group_mrp_production_request_manager"/>
|
||||
<button name="%(mrp_production_request_create_mo_action)d"
|
||||
context="{'default_mrp_production_request_id':active_id}"
|
||||
states="approved"
|
||||
string="Create Manufacturing Order" type="action"/>
|
||||
<button name="button_done" states="approved"
|
||||
string="Done" type="object" class="oe_highlight"
|
||||
groups="mrp_production_request.group_mrp_production_request_manager"/>
|
||||
<button name="button_cancel" states="to_approve,approved"
|
||||
string="Reject" type="object"
|
||||
groups="mrp_production_request.group_mrp_production_request_manager"/>
|
||||
<field name="state" widget="statusbar"
|
||||
statusbar_visible="draft,approved,done"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<label string="Manufacturing Request"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<group name="request">
|
||||
<group>
|
||||
<field name="product_id"
|
||||
domain="[('bom_ids','!=',False),('bom_ids.type','!=','phantom')]"/>
|
||||
<field name="product_tmpl_id" invisible="1"/>
|
||||
<field name="bom_id"
|
||||
domain="['&', '|', ('product_id','=',product_id),
|
||||
'&', ('product_tmpl_id.product_variant_ids','=',product_id),
|
||||
('product_id','=',False), ('type', '=', 'normal')]"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="product_qty"/>
|
||||
<field name="manufactured_qty"/>
|
||||
<field name="pending_qty"/>
|
||||
<field name="product_uom" groups="product.group_uom"/>
|
||||
<field name="category_uom_id" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group name="users">
|
||||
<field name="requested_by"/>
|
||||
<field name="assigned_to"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="description"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="mrp_production" string="Manufacturing Orders">
|
||||
<field name="mrp_production_ids"/>
|
||||
</page>
|
||||
<page name="extra" string="Extra information">
|
||||
<group>
|
||||
<group>
|
||||
<field name="procurement_id"/>
|
||||
<field name="procurement_state"/>
|
||||
<field name="origin"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="location_src_id"/>
|
||||
<field name="location_dest_id"/>
|
||||
<field name="date_planned"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mrp_production_request_tree">
|
||||
<field name="name">mrp.production.request.tree</field>
|
||||
<field name="model">mrp.production.request</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-info="state in ('draft','to_approve')"
|
||||
decoration-muted="state in ('cancel')"
|
||||
string="Manufacturing Requests">
|
||||
<field name="name"/>
|
||||
<field name="product_id"/>
|
||||
<field name="requested_by"/>
|
||||
<field name="assigned_to"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="pending_qty"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<field name="origin"/>
|
||||
<field name="date_planned"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_mrp_production_request_search" model="ir.ui.view">
|
||||
<field name="name">mrp.production.request.search</field>
|
||||
<field name="model">mrp.production.request</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Manufacturing Request">
|
||||
<field name="name" string="Manufacturing Request"/>
|
||||
<field name="mrp_production_ids" string="Manufacturing Orders"/>
|
||||
<field name="requested_by"/>
|
||||
<field name="assigned_to" />
|
||||
<field name="origin"/>
|
||||
<field name="state"/>
|
||||
<field name="pending_qty" invisible="1"/>
|
||||
<!--Filters:-->
|
||||
<filter name="unassigned" string="Unassigned"
|
||||
domain="[('assigned_to','=', False)]"
|
||||
help="Unassigned Request"/>
|
||||
<filter domain="[('assigned_to','=', uid)]"
|
||||
string="Assigned to me"/>
|
||||
<filter domain="[('requested_by','=', uid)]"
|
||||
string="My requests" help="Requested by me"/>
|
||||
<separator/>
|
||||
<filter domain="[('pending_qty','!=', 0.0)]"
|
||||
string="Pending Qty" name="pending"
|
||||
help="Request with pending quantity"/>
|
||||
<separator/>
|
||||
<filter name="state_draft" string="Draft"
|
||||
domain="[('state','=','draft')]"
|
||||
help="Request is to be approved"/>
|
||||
<filter name="state_to_approve" string="To Approve"
|
||||
domain="[('state','=','to_approve')]"
|
||||
help="Request is to be approved"/>
|
||||
<filter name="state_approved" string="Approved"
|
||||
domain="[('state','=','approved')]"
|
||||
help="Request is approved"/>
|
||||
<filter name="state_cancel" string="Cancelled"
|
||||
domain="[('state','=','cancel')]"
|
||||
help="Request is cancelled"/>
|
||||
<filter name="state_done" string="Done"
|
||||
domain="[('state','=','done')]"
|
||||
help="Request is done"/>
|
||||
<filter string="Unread Messages"
|
||||
name="message_needaction"
|
||||
domain="[('message_needaction','=',True)]"/>
|
||||
<filter domain="[('requested_by','=', uid)]"
|
||||
help="My requests"/>
|
||||
<!--Group by:-->
|
||||
<filter string="Requested by" icon="terp-personal"
|
||||
domain="[]"
|
||||
context="{'group_by':'requested_by'}"/>
|
||||
<filter string="Assigned to" icon="terp-personal"
|
||||
domain="[]"
|
||||
context="{'group_by':'assigned_to'}"/>
|
||||
<filter string="Source" icon="terp-gtk-jump-to-rtl" domain="[]"
|
||||
context="{'group_by':'origin'}"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record model="ir.actions.act_window" id="mrp_production_request_action">
|
||||
<field name="name">Manufacturing Requests</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">mrp.production.request</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{"search_default_pending":1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to start a new purchase request process.
|
||||
</p><p>
|
||||
A Manufacturing Request is an instruction to production to produce
|
||||
a certain quantity of a given product.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_mrp_production_request_act"
|
||||
sequence="10"
|
||||
parent="mrp.menu_mrp_manufacturing"
|
||||
action="mrp_production_request_action"
|
||||
/>
|
||||
|
||||
<!--Sever actions-->
|
||||
<record id="action_server_mrp_production_request_refresh"
|
||||
model="ir.actions.server">
|
||||
<field name="name">Refresh Quantities</field>
|
||||
<field name="condition">True</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="model_id" ref="model_mrp_production_request" />
|
||||
<field name="state">code</field>
|
||||
<field name="code">self._compute_manufactured_qty(cr, uid, context.get('active_ids', []), context=context)</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.values" id="action_mrp_production_request_refresh">
|
||||
<field name="name">Compute Quantities</field>
|
||||
<field name="action_id"
|
||||
ref="action_server_mrp_production_request_refresh" />
|
||||
<field name="value" eval="'ir.actions.server,' + str(ref('action_server_mrp_production_request_refresh'))" />
|
||||
<field name="key">action</field>
|
||||
<field name="model_id" ref="model_mrp_production_request" />
|
||||
<field name="model">mrp.production.request</field>
|
||||
<field name="key2">client_action_multi</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
18
mrp_production_request/views/mrp_production_view.xml
Normal file
18
mrp_production_request/views/mrp_production_view.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
|
||||
<odoo>
|
||||
|
||||
<record id="mrp_production_form_view" model="ir.ui.view">
|
||||
<field name="name">mrp.production.form - mrp_production_request</field>
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id"
|
||||
ref="mrp.mrp_production_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="move_prod_id" position="after">
|
||||
<field name="mrp_production_request_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
17
mrp_production_request/views/procurement_order_view.xml
Normal file
17
mrp_production_request/views/procurement_order_view.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="procurement_form_view">
|
||||
<field name="name">procurement.order.form - mrp_production_request</field>
|
||||
<field name="model">procurement.order</field>
|
||||
<field name="inherit_id" ref="procurement.procurement_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='origin']" position="after">
|
||||
<field name="mrp_production_request_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
18
mrp_production_request/views/product_template_view.xml
Normal file
18
mrp_production_request/views/product_template_view.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_template_property_form">
|
||||
<field name="name">product.template.form - mrp_production_request</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id"
|
||||
ref="stock.view_template_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="route_ids" position="after">
|
||||
<field name="mrp_production_request"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
5
mrp_production_request/wizards/__init__.py
Normal file
5
mrp_production_request/wizards/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import mrp_production_request_create_mo
|
||||
@@ -0,0 +1,148 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import api, fields, models, _
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
|
||||
class MrpProductionRequestCreateMo(models.TransientModel):
|
||||
_name = "mrp.production.request.create.mo"
|
||||
_description = "Wizard to create Manufacturing Orders"
|
||||
|
||||
@api.multi
|
||||
def compute_product_line_ids(self):
|
||||
self.product_line_ids.unlink()
|
||||
res = self._prepare_lines()
|
||||
product_lines = res[0]
|
||||
# workcenter_lines = res[1] # TODO: expand with workcenter_lines
|
||||
for line in product_lines:
|
||||
self.env['mrp.production.request.create.mo.line'].create(
|
||||
self._prepare_product_line(line))
|
||||
# reset workcenter_lines in production order
|
||||
# for line in workcenter_lines:
|
||||
# line['production_id'] = production.id
|
||||
# workcenter_line_obj.create(cr, uid, line, context)
|
||||
self._get_mo_qty()
|
||||
return {"type": "ir.actions.do_nothing"}
|
||||
|
||||
def _prepare_lines(self):
|
||||
"""Get the components (product_lines) and Work Centers Utilisation
|
||||
(workcenter_lines) needed for manufacturing the given a BoM.
|
||||
:return: product_lines, workcenter_lines
|
||||
"""
|
||||
bom_obj = self.env['mrp.bom']
|
||||
uom_obj = self.env['product.uom']
|
||||
bom_point = self.bom_id
|
||||
if not bom_point:
|
||||
raise UserError(_("Manufacture Request does not have a Bill of "
|
||||
"Materials defined."))
|
||||
factor = uom_obj._compute_qty(
|
||||
self.mrp_production_request_id.product_uom.id, self.pending_qty,
|
||||
bom_point.product_uom.id)
|
||||
return bom_obj._bom_explode(
|
||||
bom_point, self.mrp_production_request_id.product_id,
|
||||
factor / bom_point.product_qty,
|
||||
routing_id=self.mrp_production_request_id.routing_id.id)
|
||||
|
||||
@api.one
|
||||
def _get_mo_qty(self):
|
||||
"""Propose a qty to create a MO available to produce."""
|
||||
bottle_neck = min(self.product_line_ids.mapped('bottle_neck_factor'))
|
||||
bottle_neck = max(min(1, bottle_neck), 0)
|
||||
self.mo_qty = self.pending_qty * bottle_neck
|
||||
|
||||
mrp_production_request_id = fields.Many2one(
|
||||
comodel_name="mrp.production.request", readonly=True)
|
||||
bom_id = fields.Many2one(
|
||||
related='mrp_production_request_id.bom_id', readonly=True)
|
||||
mo_qty = fields.Float(
|
||||
string="Quantity",
|
||||
digits_compute=dp.get_precision("Product Unit of Measure"))
|
||||
pending_qty = fields.Float(
|
||||
related="mrp_production_request_id.pending_qty",
|
||||
digits_compute=dp.get_precision("Product Unit of Measure"))
|
||||
product_uom = fields.Many2one(
|
||||
related="mrp_production_request_id.product_uom")
|
||||
product_line_ids = fields.One2many(
|
||||
comodel_name="mrp.production.request.create.mo.line",
|
||||
string="Products needed",
|
||||
inverse_name="mrp_production_request_create_mo_id", readonly=True)
|
||||
|
||||
def _prepare_product_line(self, pl):
|
||||
return {
|
||||
'product_id': pl['product_id'],
|
||||
'product_qty': pl['product_qty'],
|
||||
'product_uom': pl['product_uom'],
|
||||
'mrp_production_request_create_mo_id': self.id,
|
||||
'location_id': self.mrp_production_request_id.location_src_id.id,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _prepare_manufacturing_order(self):
|
||||
self.ensure_one()
|
||||
request_id = self.mrp_production_request_id
|
||||
return {
|
||||
'product_id': request_id.product_id.id,
|
||||
'bom_id': request_id.bom_id.id,
|
||||
'product_qty': self.mo_qty,
|
||||
'product_uom': self.product_uom.id,
|
||||
'mrp_production_request_id': self.mrp_production_request_id.id,
|
||||
'origin': request_id.origin,
|
||||
'location_src_id': request_id.location_src_id.id,
|
||||
'location_dest_id': request_id.location_dest_id.id,
|
||||
'routing_id': request_id.routing_id.id,
|
||||
'move_prod_id': request_id.procurement_id.move_dest_id.id or False,
|
||||
'date_planned': request_id.date_planned,
|
||||
'company_id': request_id.company_id.id,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def create_mo(self):
|
||||
self.ensure_one()
|
||||
vals = self._prepare_manufacturing_order()
|
||||
mo = self.env['mrp.production'].create(vals)
|
||||
# Open resulting MO:
|
||||
action = self.env.ref('mrp.mrp_production_action').read()[0]
|
||||
res = self.env.ref('mrp.mrp_production_form_view')
|
||||
action.update({
|
||||
'res_id': mo and mo.id,
|
||||
'views': [(res and res.id or False, 'form')],
|
||||
})
|
||||
return action
|
||||
|
||||
|
||||
class MrpProductionRequestCreateMoLine(models.TransientModel):
|
||||
_name = "mrp.production.request.create.mo.line"
|
||||
|
||||
@api.one
|
||||
def _compute_available_qty(self):
|
||||
product_available = self.product_id.with_context(
|
||||
location=self.location_id.id)._product_available()[
|
||||
self.product_id.id]['qty_available_not_res']
|
||||
res = self.product_uom._compute_qty(
|
||||
self.product_id.product_tmpl_id.uom_id.id, product_available,
|
||||
self.product_uom.id)
|
||||
self.available_qty = res
|
||||
|
||||
@api.one
|
||||
def _compute_bottle_neck_factor(self):
|
||||
if self.product_qty:
|
||||
self.bottle_neck_factor = self.available_qty / self.product_qty
|
||||
|
||||
product_id = fields.Many2one(
|
||||
comodel_name='product.product', string='Product', required=True)
|
||||
product_qty = fields.Float(
|
||||
string='Quantity Required', required=True,
|
||||
digits_compute=dp.get_precision('Product Unit of Measure'))
|
||||
product_uom = fields.Many2one(
|
||||
comodel_name='product.uom', string='UoM', required=True)
|
||||
mrp_production_request_create_mo_id = fields.Many2one(
|
||||
comodel_name='mrp.production.request.create.mo')
|
||||
available_qty = fields.Float(
|
||||
string='Quantity Available', compute=_compute_available_qty,
|
||||
digits_compute=dp.get_precision('Product Unit of Measure'))
|
||||
bottle_neck_factor = fields.Float(compute=_compute_bottle_neck_factor)
|
||||
location_id = fields.Many2one(comodel_name='stock.location',
|
||||
required=True)
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
|
||||
<odoo>
|
||||
|
||||
<record id="mrp_production_request_create_mo_view" model="ir.ui.view">
|
||||
<field name="name">mrp.production.request.create.mo.form</field>
|
||||
<field name="model">mrp.production.request.create.mo</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Select event to register">
|
||||
<group name="origin" string="Manufacture Request status"
|
||||
col="6">
|
||||
<group colspan="2">
|
||||
<field name="mrp_production_request_id"
|
||||
options='{"no_open": True}'/>
|
||||
<field name="pending_qty"/>
|
||||
<button name="compute_product_line_ids" type="object"
|
||||
string="Compute lines" colspan="2" icon="fa-cogs"/>
|
||||
</group>
|
||||
<group colspan="4">
|
||||
<field name="product_line_ids" nolabel="1">
|
||||
<tree>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="available_qty"/>
|
||||
<field name="bottle_neck_factor"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</group>
|
||||
<group name="destination" string="Manufacturing Order:" col="4">
|
||||
<field name="mo_qty"/>
|
||||
<field name="product_uom" options="{'no_open': True}"
|
||||
groups="product.group_uom"/>
|
||||
</group>
|
||||
|
||||
<footer>
|
||||
<button name="create_mo"
|
||||
type="object"
|
||||
string="Create MO"
|
||||
class="oe_highlight"/>
|
||||
or
|
||||
<button special="cancel"
|
||||
string="Cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_production_request_create_mo_action" model="ir.actions.act_window">
|
||||
<field name="name">Create Manufacturing Order</field>
|
||||
<field name="res_model">mrp.production.request.create.mo</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user