Merge pull request #191 from Eficent/9.0-add-mrp_mto_with_stock

[9.0][ADD] mrp_mto_with_stock
This commit is contained in:
Joël Grand-Guillaume
2018-02-01 17:53:29 +01:00
committed by GitHub
10 changed files with 402 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
.. 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 MTO with Stock
==================
This module extends the functionality of Manufacturing to support the creation
of procurements when there is no stock available. This allow you to pull from
stock until the quantity on hand is zero, and then create a procurement
to fulfill the MO requirements.
Configuration
=============
To configure this module, you need to:
#. Activate the developer mode.
#. Go to the products you want to follow this behaviour.
#. In the view form go to the tab *Inventory* and set the *Manufacturing
MTO/MTS Locations*. Any other location not specified here will have the
standard behavior.
Usage
=====
To use this module, you need to:
#. Go to *Manufacturing* and create a Manufacturing Order.
#. Click on *Confirm Production*.
#. Click on *Reserve*.
.. 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
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
------------
* John Walsh <John.Walsh@interclean.com>
* Lois Rilo <lois.rilo@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.

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# Copyright 2015 John Walsh
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# Copyright 2015 John Walsh
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "MRP MTO with Stock",
"summary": "Fix Manufacturing orders to pull from stock until qty is "
"zero, and then create a procurement for them.",
"author": "John Walsh, Eficent, Odoo Community Association (OCA)",
"website": "https://odoo-community.org/",
"category": "Manufacturing",
"version": "9.0.1.0.0",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": ["mrp"],
"data": [
"views/product_template_view.xml",
"views/mrp_production_view.xml",
],
}

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# Copyright 2015 John Walsh
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import mrp_production
from . import product_template

View File

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
# Copyright 2015 John Walsh
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import api, models, _
from openerp.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class MrpProduction(models.Model):
_inherit = 'mrp.production'
@api.multi
def action_mass_assign(self):
if any([x != 'confirmed' for x in self.mapped('state')]):
raise UserError(_(
"All Manufacturing Orders must be confirmed."))
return self.action_assign()
@api.multi
def action_assign(self):
"""Reserves available products to the production order but also creates
procurements for more items if we cannot reserve enough (MTO with
stock).
@returns list of ids"""
# reserve all that is available (standard behaviour):
res = super(MrpProduction, self).action_assign()
# try to create procurements:
move_obj = self.env['stock.move']
for production in self:
for move in production.move_lines:
if (move.state == 'confirmed' and move.location_id in
move.product_id.mrp_mts_mto_location_ids):
domain = [('product_id', '=', move.product_id.id),
('move_dest_id', '=', move.id)]
if move.group_id:
domain.append(('group_id', '=', move.group_id.id))
procurement = self.env['procurement.order'].search(domain)
if not procurement:
# We have to split the move because we can't have
# a part of the move that have ancestors and not the
# other else it won't ever be reserved.
qty_to_procure = (move.remaining_qty -
move.reserved_availability)
if qty_to_procure < move.product_uom_qty:
move.do_unreserve()
new_move_id = move_obj.split(
move,
qty_to_procure,
restrict_lot_id=move.restrict_lot_id,
restrict_partner_id=move.restrict_partner_id)
new_move = move_obj.browse(
new_move_id)
move.action_assign()
else:
new_move = move
proc_dict = self._prepare_mto_procurement(
new_move, qty_to_procure)
self.env['procurement.order'].create(proc_dict)
return res
def _prepare_mto_procurement(self, move, qty):
"""Prepares a procurement for a MTO product."""
origin = ((move.group_id and move.group_id.name + ":") or "") + \
((move.name and move.name + ":") or "") + 'MTO -> Production'
group_id = move.group_id and move.group_id.id or False
route_ids = self.env.ref('stock.route_warehouse0_mto')
warehouse_id = (move.warehouse_id.id or (move.picking_type_id and
move.picking_type_id.warehouse_id.id or False))
return {
'name': move.name + ':' + str(move.id),
'origin': origin,
'company_id': move.company_id and move.company_id.id or False,
'date_planned': move.date,
'product_id': move.product_id.id,
'product_qty': qty,
'product_uom': move.product_uom.id,
'location_id': move.location_id.id,
'move_dest_id': move.id,
'group_id': group_id,
'route_ids': [(6, 0, route_ids.ids)],
'warehouse_id': warehouse_id,
'priority': move.priority,
}

View File

@@ -0,0 +1,15 @@
# -*- 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_mts_mto_location_ids = fields.Many2many(
comodel_name='stock.location',
string='Manufacturing MTO/MTS Locations',
help='These manufacturing locations will create procurements when '
'there is no stock availale in the source location.')

View 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 test_mrp_mto_with_stock

View File

@@ -0,0 +1,139 @@
# -*- 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.tests.common import TransactionCase
from openerp import fields
class TestMrpMtoWithStock(TransactionCase):
def setUp(self, *args, **kwargs):
super(TestMrpMtoWithStock, self).setUp(*args, **kwargs)
self.production_model = self.env['mrp.production']
self.bom_model = self.env['mrp.bom']
self.stock_location_stock = self.env.ref('stock.stock_location_stock')
self.manufacture_route = self.env.ref(
'mrp.route_warehouse0_manufacture')
self.uom_unit = self.env.ref('product.product_uom_unit')
self.product_fp = self.env['product.product'].create({
'name': 'FP',
'type': 'product',
'uom_id': self.uom_unit.id,
'route_ids': [(4, self.manufacture_route.id)]
})
self.product_c1 = self.env['product.product'].create({
'name': 'C1',
'type': 'product',
'uom_id': self.uom_unit.id,
'route_ids': [(4, self.manufacture_route.id)]
})
self.product_c2 = self.env['product.product'].create({
'name': 'C2',
'type': 'product',
'uom_id': self.uom_unit.id,
})
self._update_product_qty(self.product_c2,
self.stock_location_stock, 10)
self.bom_fp = self.env['mrp.bom'].create({
'product_id': self.product_fp.id,
'product_tmpl_id': self.product_fp.product_tmpl_id.id,
'bom_line_ids': ([
(0, 0, {
'product_id': self.product_c1.id,
'product_qty': 1,
'product_uom': self.uom_unit.id
}),
(0, 0, {
'product_id': self.product_c2.id,
'product_qty': 1,
'product_uom': self.uom_unit.id
}),
])
})
self.bom_c1 = self.env['mrp.bom'].create({
'product_id': self.product_c1.id,
'product_tmpl_id': self.product_c1.product_tmpl_id.id,
'bom_line_ids': ([(0, 0, {
'product_id': self.product_c2.id,
'product_qty': 1,
'product_uom': self.uom_unit.id
})])
})
self.product_c1.mrp_mts_mto_location_ids = [
(6, 0, [self.stock_location_stock.id])]
def _update_product_qty(self, product, location, quantity):
"""Update Product quantity."""
product_qty = self.env['stock.change.product.qty'].create({
'location_id': location.id,
'product_id': product.id,
'new_quantity': quantity,
})
product_qty.change_product_qty()
return product_qty
def create_procurement(self, name, product):
values = {
'name': name,
'date_planned': fields.Datetime.now(),
'product_id': product.id,
'product_qty': 4.0,
'product_uom': product.uom_id.id,
'warehouse_id': self.env.ref('stock.warehouse0').id,
'location_id': self.stock_location_stock.id,
'route_ids': [
(4, self.env.ref('mrp.route_warehouse0_manufacture').id, 0)],
}
return self.env['procurement.order'].create(values)
def test_manufacture(self):
procurement_fp = self.create_procurement('TEST/01', self.product_fp)
production_fp = procurement_fp.production_id
self.assertEqual(production_fp.state, 'confirmed')
production_fp.action_assign()
self.assertEqual(production_fp.state, 'confirmed')
procurement_c1 = self.env['procurement.order'].search(
[('product_id', '=', self.product_c1.id),
('move_dest_id', 'in', production_fp.move_lines.ids)], limit=1)
self.assertEquals(len(procurement_c1), 1)
procurement_c2 = self.env['procurement.order'].search(
[('product_id', '=', self.product_c2.id),
('move_dest_id', 'in', production_fp.move_lines.ids)], limit=1)
self.assertEquals(len(procurement_c2), 0)
procurement_c1.run()
production_c1 = procurement_c1.production_id
self.assertEqual(production_c1.state, 'confirmed')
production_c1.action_assign()
self.assertEqual(production_c1.state, 'ready')
procurement_c2 = self.env['procurement.order'].search(
[('product_id', '=', self.product_c2.id),
('move_dest_id', 'in', production_c1.move_lines.ids)], limit=1)
self.assertEquals(len(procurement_c2), 0)
wizard = self.env['mrp.product.produce'].create({
'product_id': self.product_c1.id,
'product_qty': 1,
})
self.env['mrp.production'].action_produce(
production_c1.id, 1, 'consume_produce', wizard)
production_c1.refresh()
self.assertEqual(production_fp.state, 'confirmed')
wizard = self.env['mrp.product.produce'].create({
'product_id': self.product_c1.id,
'product_qty': 3,
})
self.env['mrp.production'].action_produce(
production_c1.id, 3, 'consume_produce', wizard)
production_c1.refresh()
self.assertEqual(production_fp.state, 'ready')

View File

@@ -0,0 +1,28 @@
<?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>
<record id="action_server_mrp_production_mass_assign"
model="ir.actions.server">
<field name="name">Reserve MO</field>
<field name="condition">True</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_mrp_production" />
<field name="state">code</field>
<field name="code">self.action_mass_assign(cr, uid, context.get('active_ids', []), context=context)</field>
</record>
<record model="ir.values" id="action_mrp_production_mass_assign">
<field name="name">mrp.production.action - mass assign</field>
<field name="action_id"
ref="action_server_mrp_production_mass_assign" />
<field name="value" eval="'ir.actions.server,' + str(ref('action_server_mrp_production_mass_assign'))" />
<field name="key">action</field>
<field name="model_id" ref="model_mrp_production" />
<field name="model">mrp.production</field>
<field name="key2">client_action_multi</field>
</record>
</odoo>

View File

@@ -0,0 +1,20 @@
<?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>
<record id="view_template_property_form" model="ir.ui.view">
<field name="name">product.template.form - mrp_mto_with_stock
extension</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.view_template_property_form"/>
<field name="arch" type="xml">
<field name="property_stock_inventory" position="after">
<field name="mrp_mts_mto_location_ids" widget="many2many_tags"
options="{'no_create': True}"/>
</field>
</field>
</record>
</odoo>