From 63fb663bf3d3cc577c4a5325df66e8015206be63 Mon Sep 17 00:00:00 2001 From: John Walsh Date: Wed, 23 Sep 2015 12:27:50 -0700 Subject: [PATCH 1/6] [ADD] mrp_mto_with_stock - initial commit --- mrp_mto_with_stock/__init__.py | 18 ++++++ mrp_mto_with_stock/__openerp__.py | 47 +++++++++++++++ mrp_mto_with_stock/models/__init__.py | 19 ++++++ mrp_mto_with_stock/models/mrp.py | 86 +++++++++++++++++++++++++++ mrp_mto_with_stock/models/stock.py | 17 ++++++ 5 files changed, 187 insertions(+) create mode 100644 mrp_mto_with_stock/__init__.py create mode 100644 mrp_mto_with_stock/__openerp__.py create mode 100644 mrp_mto_with_stock/models/__init__.py create mode 100644 mrp_mto_with_stock/models/mrp.py create mode 100644 mrp_mto_with_stock/models/stock.py diff --git a/mrp_mto_with_stock/__init__.py b/mrp_mto_with_stock/__init__.py new file mode 100644 index 000000000..d0c2e2f37 --- /dev/null +++ b/mrp_mto_with_stock/__init__.py @@ -0,0 +1,18 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +############################################################################## +import models diff --git a/mrp_mto_with_stock/__openerp__.py b/mrp_mto_with_stock/__openerp__.py new file mode 100644 index 000000000..5e408e726 --- /dev/null +++ b/mrp_mto_with_stock/__openerp__.py @@ -0,0 +1,47 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +############################################################################## +{ + 'name': "mrp_mto_with_stock", + + 'summary': """ + Fix Manufacturing orders to pull from stock until qty is zero, + and then create a procurement for them""", + + 'description': """ + Long description of module's purpose + """, + + 'author': "John Walsh", + 'website': "http://github.com/michaeljohn32", + + # Categories can be used to filter modules in modules listing + # Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml + # for the full list + 'category': 'Hidden/Dependency', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['mrp', 'stock_mts_mto_rule'], + + # always loaded + 'data': [ + ], + # only loaded in demonstration mode + 'demo': [ + ], +} diff --git a/mrp_mto_with_stock/models/__init__.py b/mrp_mto_with_stock/models/__init__.py new file mode 100644 index 000000000..e9ebf62ff --- /dev/null +++ b/mrp_mto_with_stock/models/__init__.py @@ -0,0 +1,19 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +############################################################################## +import stock +import mrp diff --git a/mrp_mto_with_stock/models/mrp.py b/mrp_mto_with_stock/models/mrp.py new file mode 100644 index 000000000..cec522e6c --- /dev/null +++ b/mrp_mto_with_stock/models/mrp.py @@ -0,0 +1,86 @@ +# -*- encoding: utf-8 -*- +from openerp import fields, models, api +import pdb +import logging +_logger = logging.getLogger(__name__) + +class mrp_production(models.Model): + _inherit = 'mrp.production' + +# @api.model +# def _make_consume_line_from_data(self, production, product, uom_id, qty, uos_id, uos_qty): +# '''Confirms stock move or put it in waiting if it's linked to another move. +# @returns list of ids''' +# pdb.set_trace() +# # change the qty to make two moves (if needed) +# res = super(mrp_production, self)._make_consume_line_from_data(production, product, uom_id, uos_id, uos_qty) +# return res + @api.one + def action_confirm(self): + '''Confirms stock move or put it in waiting if it's linked to another move. + @returns list of ids''' +# pdb.set_trace() + # change the qty to make two moves (if needed) + res = super(mrp_production, self).action_confirm() + # try to assign moves (and generate procurements!) + self.action_assign() + return res + + @api.one + 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 + res = super(mrp_production, self).action_assign() + mtos_route = self.env.ref('stock_mts_mto_rule.route_mto_mts') + for move in self.move_lines: + if move.state == 'confirmed' and mtos_route.id in move.product_id.route_ids.ids: + #This move is waiting availability + + #create a domain + #TODO: check other possible states confirmed/exception? + domain = [('product_id','=', move.product_id.id),('state','=','running'),('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 need to create a procurement + qty_to_procure = move.remaining_qty - move.reserved_availability + proc_dict = self._prepare_mto_procurement(move, qty_to_procure) + procurement = self.env['procurement.order'].create(proc_dict) + return res + + def _prepare_mto_procurement(self, move, qty): + '''Prepares a procurement for a MTO move + using similar logic to /stock/stock.py/class stock_move/_prepare_procurement_from_move() + + ''' + 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')] + 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, + 'product_uos_qty': qty, #FIXME: (move.product_uos and move.product_uos_qty) or move.product_uom_qty, + 'product_uos': move.product_uom.id, #FIXME:(move.product_uos and move.product_uos.id) or move.product_uom.id, + 'location_id': move.location_id.id, + 'move_dest_id': move.id, + 'group_id': group_id, + 'route_ids':[(4, x.id) for x in route_ids], + 'warehouse_id': move.warehouse_id.id or (move.picking_type_id and move.picking_type_id.warehouse_id.id or False), + 'priority': move.priority, + } + + + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/mrp_mto_with_stock/models/stock.py b/mrp_mto_with_stock/models/stock.py new file mode 100644 index 000000000..f67c7b522 --- /dev/null +++ b/mrp_mto_with_stock/models/stock.py @@ -0,0 +1,17 @@ +# -*- encoding: utf-8 -*- +from openerp import fields, models, api +import pdb + +class stock_move(models.Model): + _inherit = 'stock.move' + + @api.multi + def action_confirm(self): + '''Confirms stock move or put it in waiting if it's linked to another move. + @returns list of ids''' + res = super(stock_move, self).action_confirm() + return res + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + From 031578e7b01806b615077151a947c8a993311fbf Mon Sep 17 00:00:00 2001 From: lreficent Date: Wed, 26 Apr 2017 18:38:39 +0200 Subject: [PATCH 2/6] [9.0][IMP] mrp_mto_with_stock: adapt to OCA and minor fixes. --- mrp_mto_with_stock/README.rst | 71 ++++++++++++++++++++ mrp_mto_with_stock/__init__.py | 24 ++----- mrp_mto_with_stock/__openerp__.py | 61 +++++------------- mrp_mto_with_stock/models/__init__.py | 25 ++----- mrp_mto_with_stock/models/mrp.py | 93 ++++++++++++--------------- mrp_mto_with_stock/models/stock.py | 17 ----- 6 files changed, 139 insertions(+), 152 deletions(-) create mode 100644 mrp_mto_with_stock/README.rst delete mode 100644 mrp_mto_with_stock/models/stock.py diff --git a/mrp_mto_with_stock/README.rst b/mrp_mto_with_stock/README.rst new file mode 100644 index 000000000..197e0816f --- /dev/null +++ b/mrp_mto_with_stock/README.rst @@ -0,0 +1,71 @@ +.. 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 +for 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 got to the tab *Inventory* and check the box for the + route *Make To Order + Make To Stock*. + +Usage +===== + +To use this module, you need to: + +#. Go to *Manufacturing* and create a Manufacturing Order. +#. Click on *Confirm Production*. + +.. 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 +`_. 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 `_. + +Contributors +------------ + +* John Walsh +* Lois Rilo + +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. diff --git a/mrp_mto_with_stock/__init__.py b/mrp_mto_with_stock/__init__.py index d0c2e2f37..a7129c69a 100644 --- a/mrp_mto_with_stock/__init__.py +++ b/mrp_mto_with_stock/__init__.py @@ -1,18 +1,6 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# -############################################################################## -import models +# -*- 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 diff --git a/mrp_mto_with_stock/__openerp__.py b/mrp_mto_with_stock/__openerp__.py index 5e408e726..3235afb03 100644 --- a/mrp_mto_with_stock/__openerp__.py +++ b/mrp_mto_with_stock/__openerp__.py @@ -1,47 +1,18 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# -############################################################################## +# -*- 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""", - - 'description': """ - Long description of module's purpose - """, - - 'author': "John Walsh", - 'website': "http://github.com/michaeljohn32", - - # Categories can be used to filter modules in modules listing - # Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml - # for the full list - 'category': 'Hidden/Dependency', - 'version': '0.1', - - # any module necessary for this one to work correctly - 'depends': ['mrp', 'stock_mts_mto_rule'], - - # always loaded - 'data': [ - ], - # only loaded in demonstration mode - 'demo': [ - ], + "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", "stock_mts_mto_rule"], } diff --git a/mrp_mto_with_stock/models/__init__.py b/mrp_mto_with_stock/models/__init__.py index e9ebf62ff..f5102a7f2 100644 --- a/mrp_mto_with_stock/models/__init__.py +++ b/mrp_mto_with_stock/models/__init__.py @@ -1,19 +1,6 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# -############################################################################## -import stock -import mrp +# -*- 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 diff --git a/mrp_mto_with_stock/models/mrp.py b/mrp_mto_with_stock/models/mrp.py index cec522e6c..a2d5565b7 100644 --- a/mrp_mto_with_stock/models/mrp.py +++ b/mrp_mto_with_stock/models/mrp.py @@ -1,67 +1,62 @@ -# -*- encoding: utf-8 -*- -from openerp import fields, models, api -import pdb +# -*- 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 import logging _logger = logging.getLogger(__name__) -class mrp_production(models.Model): + +class MrpProduction(models.Model): _inherit = 'mrp.production' -# @api.model -# def _make_consume_line_from_data(self, production, product, uom_id, qty, uos_id, uos_qty): -# '''Confirms stock move or put it in waiting if it's linked to another move. -# @returns list of ids''' -# pdb.set_trace() -# # change the qty to make two moves (if needed) -# res = super(mrp_production, self)._make_consume_line_from_data(production, product, uom_id, uos_id, uos_qty) -# return res @api.one def action_confirm(self): - '''Confirms stock move or put it in waiting if it's linked to another move. - @returns list of ids''' -# pdb.set_trace() + """Confirms stock move or put it in waiting if it's linked to another move. + @returns list of ids""" # change the qty to make two moves (if needed) - res = super(mrp_production, self).action_confirm() + res = super(MrpProduction, self).action_confirm() # try to assign moves (and generate procurements!) self.action_assign() return res @api.one 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 - res = super(mrp_production, self).action_assign() + """Reserves available products to the production order but also reates + 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: mtos_route = self.env.ref('stock_mts_mto_rule.route_mto_mts') for move in self.move_lines: - if move.state == 'confirmed' and mtos_route.id in move.product_id.route_ids.ids: - #This move is waiting availability - - #create a domain - #TODO: check other possible states confirmed/exception? - domain = [('product_id','=', move.product_id.id),('state','=','running'),('move_dest_id','=',move.id)] + if (move.state == 'confirmed' and mtos_route.id in + move.product_id.route_ids.ids): + domain = [('product_id', '=', move.product_id.id), + ('state', '=', 'running'), + ('move_dest_id', '=', move.id)] if move.group_id: - domain.append(('group_id','=',move.group_id.id)) + domain.append(('group_id', '=', move.group_id.id)) procurement = self.env['procurement.order'].search(domain) if not procurement: - # we need to create a procurement - qty_to_procure = move.remaining_qty - move.reserved_availability - proc_dict = self._prepare_mto_procurement(move, qty_to_procure) - procurement = self.env['procurement.order'].create(proc_dict) + qty_to_procure = (move.remaining_qty - + move.reserved_availability) + proc_dict = self._prepare_mto_procurement( + 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 move - using similar logic to /stock/stock.py/class stock_move/_prepare_procurement_from_move() - - ''' - 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')] - return{ + 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, @@ -69,18 +64,10 @@ class mrp_production(models.Model): 'product_id': move.product_id.id, 'product_qty': qty, 'product_uom': move.product_uom.id, - 'product_uos_qty': qty, #FIXME: (move.product_uos and move.product_uos_qty) or move.product_uom_qty, - 'product_uos': move.product_uom.id, #FIXME:(move.product_uos and move.product_uos.id) or move.product_uom.id, 'location_id': move.location_id.id, 'move_dest_id': move.id, 'group_id': group_id, - 'route_ids':[(4, x.id) for x in route_ids], - 'warehouse_id': move.warehouse_id.id or (move.picking_type_id and move.picking_type_id.warehouse_id.id or False), + 'route_ids': [(6, 0, route_ids.ids)], + 'warehouse_id': warehouse_id, 'priority': move.priority, } - - - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/mrp_mto_with_stock/models/stock.py b/mrp_mto_with_stock/models/stock.py deleted file mode 100644 index f67c7b522..000000000 --- a/mrp_mto_with_stock/models/stock.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- encoding: utf-8 -*- -from openerp import fields, models, api -import pdb - -class stock_move(models.Model): - _inherit = 'stock.move' - - @api.multi - def action_confirm(self): - '''Confirms stock move or put it in waiting if it's linked to another move. - @returns list of ids''' - res = super(stock_move, self).action_confirm() - return res - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - From 410095fd1e90fe9f6a97d03e097e26f2fc51c4ca Mon Sep 17 00:00:00 2001 From: lreficent Date: Wed, 3 May 2017 19:00:33 +0200 Subject: [PATCH 3/6] [9.0][REW] mrp_mto_with_stock: Rework to remove dependency and enhance flexibility --- mrp_mto_with_stock/README.rst | 5 +++-- mrp_mto_with_stock/__openerp__.py | 3 ++- mrp_mto_with_stock/models/__init__.py | 1 + mrp_mto_with_stock/models/mrp.py | 5 ++--- mrp_mto_with_stock/models/product_template.py | 15 ++++++++++++++ .../views/product_template_view.xml | 20 +++++++++++++++++++ 6 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 mrp_mto_with_stock/models/product_template.py create mode 100644 mrp_mto_with_stock/views/product_template_view.xml diff --git a/mrp_mto_with_stock/README.rst b/mrp_mto_with_stock/README.rst index 197e0816f..5e9c0f9fb 100644 --- a/mrp_mto_with_stock/README.rst +++ b/mrp_mto_with_stock/README.rst @@ -18,8 +18,9 @@ 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 got to the tab *Inventory* and check the box for the - route *Make To Order + Make To Stock*. +#. 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 ===== diff --git a/mrp_mto_with_stock/__openerp__.py b/mrp_mto_with_stock/__openerp__.py index 3235afb03..928dbc421 100644 --- a/mrp_mto_with_stock/__openerp__.py +++ b/mrp_mto_with_stock/__openerp__.py @@ -14,5 +14,6 @@ "license": "AGPL-3", "application": False, "installable": True, - "depends": ["mrp", "stock_mts_mto_rule"], + "depends": ["mrp"], + "data": ['views/product_template_view.xml'], } diff --git a/mrp_mto_with_stock/models/__init__.py b/mrp_mto_with_stock/models/__init__.py index f5102a7f2..709cb97b3 100644 --- a/mrp_mto_with_stock/models/__init__.py +++ b/mrp_mto_with_stock/models/__init__.py @@ -4,3 +4,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import mrp +from . import product_template diff --git a/mrp_mto_with_stock/models/mrp.py b/mrp_mto_with_stock/models/mrp.py index a2d5565b7..87d1c082f 100644 --- a/mrp_mto_with_stock/models/mrp.py +++ b/mrp_mto_with_stock/models/mrp.py @@ -30,10 +30,9 @@ class MrpProduction(models.Model): # reserve all that is available (standard behaviour): res = super(MrpProduction, self).action_assign() # try to create procurements: - mtos_route = self.env.ref('stock_mts_mto_rule.route_mto_mts') for move in self.move_lines: - if (move.state == 'confirmed' and mtos_route.id in - move.product_id.route_ids.ids): + if (move.state == 'confirmed' and move.location_id in + move.product_id.mrp_mts_mto_location_ids): domain = [('product_id', '=', move.product_id.id), ('state', '=', 'running'), ('move_dest_id', '=', move.id)] diff --git a/mrp_mto_with_stock/models/product_template.py b/mrp_mto_with_stock/models/product_template.py new file mode 100644 index 000000000..11099600d --- /dev/null +++ b/mrp_mto_with_stock/models/product_template.py @@ -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.') diff --git a/mrp_mto_with_stock/views/product_template_view.xml b/mrp_mto_with_stock/views/product_template_view.xml new file mode 100644 index 000000000..cbe221f8f --- /dev/null +++ b/mrp_mto_with_stock/views/product_template_view.xml @@ -0,0 +1,20 @@ + + + + + + + product.template.form - mrp_mto_with_stock + extension + product.template + + + + + + + + + From 87a2537c5efd72447ba1d6910a3d7b13acb7b894 Mon Sep 17 00:00:00 2001 From: Jordi Ballester Date: Sun, 23 Jul 2017 07:11:58 +0200 Subject: [PATCH 4/6] [IMP] should not auto-confirm the MO. Added test cases --- mrp_mto_with_stock/README.rst | 2 +- mrp_mto_with_stock/models/__init__.py | 2 +- .../models/{mrp.py => mrp_production.py} | 13 +- mrp_mto_with_stock/tests/__init__.py | 5 + .../tests/test_mrp_mto_with_stock.py | 139 ++++++++++++++++++ 5 files changed, 147 insertions(+), 14 deletions(-) rename mrp_mto_with_stock/models/{mrp.py => mrp_production.py} (85%) create mode 100644 mrp_mto_with_stock/tests/__init__.py create mode 100644 mrp_mto_with_stock/tests/test_mrp_mto_with_stock.py diff --git a/mrp_mto_with_stock/README.rst b/mrp_mto_with_stock/README.rst index 5e9c0f9fb..4c9ffdd47 100644 --- a/mrp_mto_with_stock/README.rst +++ b/mrp_mto_with_stock/README.rst @@ -9,7 +9,7 @@ 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 -for fulfill the MO requirements. +to fulfill the MO requirements. Configuration ============= diff --git a/mrp_mto_with_stock/models/__init__.py b/mrp_mto_with_stock/models/__init__.py index 709cb97b3..c4a684fa2 100644 --- a/mrp_mto_with_stock/models/__init__.py +++ b/mrp_mto_with_stock/models/__init__.py @@ -3,5 +3,5 @@ # Copyright 2015 John Walsh # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import mrp +from . import mrp_production from . import product_template diff --git a/mrp_mto_with_stock/models/mrp.py b/mrp_mto_with_stock/models/mrp_production.py similarity index 85% rename from mrp_mto_with_stock/models/mrp.py rename to mrp_mto_with_stock/models/mrp_production.py index 87d1c082f..281fc9ea8 100644 --- a/mrp_mto_with_stock/models/mrp.py +++ b/mrp_mto_with_stock/models/mrp_production.py @@ -11,19 +11,9 @@ _logger = logging.getLogger(__name__) class MrpProduction(models.Model): _inherit = 'mrp.production' - @api.one - def action_confirm(self): - """Confirms stock move or put it in waiting if it's linked to another move. - @returns list of ids""" - # change the qty to make two moves (if needed) - res = super(MrpProduction, self).action_confirm() - # try to assign moves (and generate procurements!) - self.action_assign() - return res - @api.one def action_assign(self): - """Reserves available products to the production order but also reates + """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""" @@ -34,7 +24,6 @@ class MrpProduction(models.Model): if (move.state == 'confirmed' and move.location_id in move.product_id.mrp_mts_mto_location_ids): domain = [('product_id', '=', move.product_id.id), - ('state', '=', 'running'), ('move_dest_id', '=', move.id)] if move.group_id: domain.append(('group_id', '=', move.group_id.id)) diff --git a/mrp_mto_with_stock/tests/__init__.py b/mrp_mto_with_stock/tests/__init__.py new file mode 100644 index 000000000..f8065ee2e --- /dev/null +++ b/mrp_mto_with_stock/tests/__init__.py @@ -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 diff --git a/mrp_mto_with_stock/tests/test_mrp_mto_with_stock.py b/mrp_mto_with_stock/tests/test_mrp_mto_with_stock.py new file mode 100644 index 000000000..464195aa9 --- /dev/null +++ b/mrp_mto_with_stock/tests/test_mrp_mto_with_stock.py @@ -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') From 501495a3c31d569bd86516a624f46434ef1bd648 Mon Sep 17 00:00:00 2001 From: lreficent Date: Thu, 27 Jul 2017 16:11:39 +0200 Subject: [PATCH 5/6] [IMP] add a server action to massively assign MOs. --- mrp_mto_with_stock/__openerp__.py | 5 +++- mrp_mto_with_stock/models/mrp_production.py | 10 ++++++- .../views/mrp_production_view.xml | 28 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 mrp_mto_with_stock/views/mrp_production_view.xml diff --git a/mrp_mto_with_stock/__openerp__.py b/mrp_mto_with_stock/__openerp__.py index 928dbc421..08fb4650a 100644 --- a/mrp_mto_with_stock/__openerp__.py +++ b/mrp_mto_with_stock/__openerp__.py @@ -15,5 +15,8 @@ "application": False, "installable": True, "depends": ["mrp"], - "data": ['views/product_template_view.xml'], + "data": [ + "views/product_template_view.xml", + "views/mrp_production_view.xml", + ], } diff --git a/mrp_mto_with_stock/models/mrp_production.py b/mrp_mto_with_stock/models/mrp_production.py index 281fc9ea8..87e0d107f 100644 --- a/mrp_mto_with_stock/models/mrp_production.py +++ b/mrp_mto_with_stock/models/mrp_production.py @@ -3,7 +3,8 @@ # Copyright 2015 John Walsh # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import api, models +from openerp import api, models, _ +from openerp.exceptions import UserError import logging _logger = logging.getLogger(__name__) @@ -11,6 +12,13 @@ _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.one def action_assign(self): """Reserves available products to the production order but also creates diff --git a/mrp_mto_with_stock/views/mrp_production_view.xml b/mrp_mto_with_stock/views/mrp_production_view.xml new file mode 100644 index 000000000..6d25e52e1 --- /dev/null +++ b/mrp_mto_with_stock/views/mrp_production_view.xml @@ -0,0 +1,28 @@ + + + + + + + Reserve MO + True + ir.actions.server + + code + self.action_mass_assign(cr, uid, context.get('active_ids', []), context=context) + + + + mrp.production.action - mass assign + + + action + + mrp.production + client_action_multi + + + From 3eb1e7e65ae19d77e27fd66378345e363a770c31 Mon Sep 17 00:00:00 2001 From: lreficent Date: Fri, 28 Jul 2017 09:31:38 +0200 Subject: [PATCH 6/6] [FIX] split move to be able to reserve properly when available. --- mrp_mto_with_stock/README.rst | 1 + mrp_mto_with_stock/models/mrp_production.py | 48 ++++++++++++++------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/mrp_mto_with_stock/README.rst b/mrp_mto_with_stock/README.rst index 4c9ffdd47..b004f5b12 100644 --- a/mrp_mto_with_stock/README.rst +++ b/mrp_mto_with_stock/README.rst @@ -29,6 +29,7 @@ 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 diff --git a/mrp_mto_with_stock/models/mrp_production.py b/mrp_mto_with_stock/models/mrp_production.py index 87e0d107f..b8eabe2eb 100644 --- a/mrp_mto_with_stock/models/mrp_production.py +++ b/mrp_mto_with_stock/models/mrp_production.py @@ -19,7 +19,7 @@ class MrpProduction(models.Model): "All Manufacturing Orders must be confirmed.")) return self.action_assign() - @api.one + @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 @@ -28,20 +28,38 @@ class MrpProduction(models.Model): # reserve all that is available (standard behaviour): res = super(MrpProduction, self).action_assign() # try to create procurements: - for move in self.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: - qty_to_procure = (move.remaining_qty - - move.reserved_availability) - proc_dict = self._prepare_mto_procurement( - move, qty_to_procure) - self.env['procurement.order'].create(proc_dict) + 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):