diff --git a/mrp_mto_with_stock/README.rst b/mrp_mto_with_stock/README.rst index fa154d0f29e..b8606761ec7 100644 --- a/mrp_mto_with_stock/README.rst +++ b/mrp_mto_with_stock/README.rst @@ -29,6 +29,7 @@ To configure this module, you need to: standard behavior. If you want to use the second mode, based on forecast quantity + #. Go to the warehouse you want to follow this behaviour. #. In the view form go to the tab *Warehouse Configuration* and set the *MRP MTO with forecast stock*. You still need to configure the products @@ -44,7 +45,7 @@ To use this module, you need to: .. 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/10.0 + :target: https://runbot.odoo-community.org/runbot/129/11.0 Bug Tracker =========== @@ -68,6 +69,7 @@ Contributors * John Walsh * Lois Rilo * Florian da Costa +* Bhavesh Odedra Maintainer ---------- diff --git a/mrp_mto_with_stock/__init__.py b/mrp_mto_with_stock/__init__.py index a7129c69a68..69f7babdfb1 100644 --- a/mrp_mto_with_stock/__init__.py +++ b/mrp_mto_with_stock/__init__.py @@ -1,6 +1,3 @@ -# -*- 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/__manifest__.py b/mrp_mto_with_stock/__manifest__.py index ef669a7fbc0..fb12f2dff2e 100644 --- a/mrp_mto_with_stock/__manifest__.py +++ b/mrp_mto_with_stock/__manifest__.py @@ -1,4 +1,3 @@ -# -*- 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). @@ -10,11 +9,11 @@ "author": "John Walsh, Eficent, Odoo Community Association (OCA)", "website": "https://odoo-community.org/", "category": "Manufacturing", - "version": "10.0.1.0.0", + "version": "11.0.1.0.0", "license": "AGPL-3", "application": False, "installable": True, - "depends": ["mrp"], + "depends": ["stock", "sale", "purchase", "mrp"], "data": [ 'views/product_template_view.xml', 'views/stock_warehouse.xml', diff --git a/mrp_mto_with_stock/demo/product.xml b/mrp_mto_with_stock/demo/product.xml index 4a4d61a8cac..fefb255f20f 100644 --- a/mrp_mto_with_stock/demo/product.xml +++ b/mrp_mto_with_stock/demo/product.xml @@ -1,128 +1,123 @@ - + - - - - TOP - - 600.00 - 400.00 - product - - - TODO - MANUF - - - - - Subproduct 1 - - 300.00 - 100.00 - product - - - TODO - MANUF 1-1 - - - - - - Subproduct 2 - - 100.00 - 30.00 - product - - - TODO - MANUF 1-2 - - - - - - Subproduct 1-1 - - 10.00 - 3.00 - product - - - TODO - MANUF 1-1-1 - - - - - Subproduct 2-1 - - 10.00 - 3.00 - product - - - TODO - MANUF 1-2-1 - - - - - - - 10 - - - - - 5 - - 1 - - - - - - 2 - - 1 - - - - - - - 10 - - - - - 2 - - 1 - - - - - - - 10 - - - - - 4 - - 1 - - - - - + + + TOP + + 600.00 + 400.00 + product + + + TODO + MANUF + + + + + Subproduct 1 + + 300.00 + 100.00 + product + + + TODO + MANUF 1-1 + + + + + + Subproduct 2 + + 100.00 + 30.00 + product + + + TODO + MANUF 1-2 + + + + + + Subproduct 1-1 + + 10.00 + 3.00 + product + + + TODO + MANUF 1-1-1 + + + + Subproduct 2-1 + + 10.00 + 3.00 + product + + + TODO + MANUF 1-2-1 + + + + + + 10 + + + + + 5 + + 1 + + + + + + 2 + + 1 + + + + + + + 10 + + + + + 2 + + 1 + + + + + + + 10 + + + + + 4 + + 1 + + + diff --git a/mrp_mto_with_stock/models/__init__.py b/mrp_mto_with_stock/models/__init__.py index c617a225fc9..6a98762c4cb 100644 --- a/mrp_mto_with_stock/models/__init__.py +++ b/mrp_mto_with_stock/models/__init__.py @@ -1,6 +1,3 @@ -# -*- 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 diff --git a/mrp_mto_with_stock/models/mrp_production.py b/mrp_mto_with_stock/models/mrp_production.py index dc6a585f534..c438e331acf 100644 --- a/mrp_mto_with_stock/models/mrp_production.py +++ b/mrp_mto_with_stock/models/mrp_production.py @@ -1,9 +1,9 @@ -# -*- 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 odoo import api, models +from odoo.exceptions import UserError import logging _logger = logging.getLogger(__name__) @@ -11,10 +11,9 @@ class MrpProduction(models.Model): _inherit = 'mrp.production' - @api.multi - def _adjust_procure_method(self): - # Si location => By pass method... - super(MrpProduction, self)._adjust_procure_method() + def _get_procurement_group_data(self, move): + return {'partner_id': move.partner_id.id, + 'name': '{0}->{1}'.format(self.name, move.product_id.name)} @api.multi def action_assign(self): @@ -26,78 +25,76 @@ def action_assign(self): res = super(MrpProduction, self).action_assign() # try to create procurements: move_obj = self.env['stock.move'] + procurement_obj = self.env['procurement.group'] for production in self: warehouse = production.location_src_id.get_warehouse() mto_with_no_move_dest_id = warehouse.mrp_mto_mts_forecast_qty for move in self.move_raw_ids: - if (move.state == 'confirmed' and move.location_id in - move.product_id.mrp_mts_mto_location_ids and not - mto_with_no_move_dest_id): - 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) + group = new_move = procurement = False + qty_to_procure = 0.0 + if move.state in ('partially_available', 'confirmed') and \ + not mto_with_no_move_dest_id and \ + move.location_id in \ + move.product_id.mrp_mts_mto_location_ids: + # Search procurement group which has created from here + group_name = '{0}->{1}'.format( + self.name, move.product_id.name) + procurement = procurement_obj.search( + [('name', '=', group_name)]) 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) + qty_to_procure = ( + move.product_uom_qty - move.reserved_availability) if qty_to_procure < move.product_uom_qty: - move.do_unreserve() - new_move_id = move.split( + move._do_unreserve() + new_move_id = move._split( 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() + 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, - mto_with_no_move_dest_id) - self.env['procurement.order'].create(proc_dict) - - if (move.state == 'confirmed' and move.location_id in - move.product_id.mrp_mts_mto_location_ids and - move.procure_method == 'make_to_stock' and - mto_with_no_move_dest_id): + pg_data = production._get_procurement_group_data( + new_move) + group = procurement_obj.create(pg_data) + if move.state in ('partially_available','confirmed') and \ + move.procure_method == 'make_to_stock' and \ + mto_with_no_move_dest_id and \ + move.location_id in \ + move.product_id.mrp_mts_mto_location_ids: qty_to_procure = production.get_mto_qty_to_procure(move) if qty_to_procure > 0.0: - proc_dict = self._prepare_mto_procurement( - move, qty_to_procure, mto_with_no_move_dest_id) - proc_dict.pop('move_dest_id', None) - self.env['procurement.order'].create(proc_dict) + pg_data = production._get_procurement_group_data(move) + group = procurement_obj.create(pg_data) + new_move = move + if group: + production.run_procurement(new_move, group, qty_to_procure) return res - def _prepare_mto_procurement(self, move, qty, mto_with_no_move_dest_id): - """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)) - vals = { - '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, - 'group_id': group_id, - 'route_ids': [(6, 0, route_ids.ids)], - 'warehouse_id': warehouse_id, - 'priority': move.priority, - } - if not mto_with_no_move_dest_id: - vals['move_dest_id'] = move.id - return vals + @api.multi + def run_procurement(self, move, group, qty): + self.ensure_one() + errors = [] + values = move._prepare_procurement_values() + origin = ((group and group.name + ":") or "") + 'MTO -> Production' + values['route_ids'] = self.env.ref('stock.route_warehouse0_mto') + try: + self.env['procurement.group'].run( + move.product_id, + qty, + move.product_uom, + move.location_id, + origin, + origin, + values + ) + except UserError as error: + errors.append(error.name) + if errors: + raise UserError('\n'.join(errors)) + return True @api.multi def get_mto_qty_to_procure(self, move): diff --git a/mrp_mto_with_stock/models/product_template.py b/mrp_mto_with_stock/models/product_template.py index a6e1142bf4e..6e8624deacf 100644 --- a/mrp_mto_with_stock/models/product_template.py +++ b/mrp_mto_with_stock/models/product_template.py @@ -1,4 +1,3 @@ -# -*- 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). diff --git a/mrp_mto_with_stock/models/stock_warehouse.py b/mrp_mto_with_stock/models/stock_warehouse.py index 75d3f30f0d1..2e2fe213c43 100644 --- a/mrp_mto_with_stock/models/stock_warehouse.py +++ b/mrp_mto_with_stock/models/stock_warehouse.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 Akretion # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/mrp_mto_with_stock/static/description/icon.png b/mrp_mto_with_stock/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/mrp_mto_with_stock/static/description/icon.png differ diff --git a/mrp_mto_with_stock/tests/__init__.py b/mrp_mto_with_stock/tests/__init__.py index f8065ee2e46..575f33c5824 100644 --- a/mrp_mto_with_stock/tests/__init__.py +++ b/mrp_mto_with_stock/tests/__init__.py @@ -1,5 +1,3 @@ -# -*- 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 index c9d844cde87..d4cb4b7af41 100644 --- a/mrp_mto_with_stock/tests/test_mrp_mto_with_stock.py +++ b/mrp_mto_with_stock/tests/test_mrp_mto_with_stock.py @@ -1,4 +1,3 @@ -# -*- 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). @@ -10,6 +9,7 @@ class TestMrpMtoWithStock(TransactionCase): def setUp(self, *args, **kwargs): super(TestMrpMtoWithStock, self).setUp(*args, **kwargs) self.production_model = self.env['mrp.production'] + self.procurement_model = self.env['procurement.group'] self.bom_model = self.env['mrp.bom'] self.stock_location_stock = self.env.ref('stock.stock_location_stock') self.manufacture_route = self.env.ref( @@ -67,20 +67,11 @@ def test_manufacture_with_forecast_stock(self): self.production.action_assign() self.assertEqual(self.production.availability, 'partially_available') - self.assertEquals(self.subproduct1.virtual_available, 0) - - procurement_subproduct1 = self.env['procurement.order'].search( - [('product_id', '=', self.subproduct1.id), - ('group_id', '=', self.production.procurement_group_id.id)]) - - self.assertEquals(len(procurement_subproduct1), 1) - self.assertEquals(procurement_subproduct1.product_qty, 3) - - production_sub1 = procurement_subproduct1.production_id + production_sub1 = self.production_model.search( + [('origin', 'ilike', self.production.name)]) self.assertEqual(production_sub1.state, 'confirmed') self.assertEqual(production_sub1.product_qty, 3) - self._update_product_qty(self.subproduct1, self.stock_location_stock, 7) @@ -88,20 +79,18 @@ def test_manufacture_with_forecast_stock(self): self.production2 = self.production_model.create( self._get_production_vals()) self.production2.action_assign() - procurement_subproduct1_2 = self.env['procurement.order'].search( - [('product_id', '=', self.subproduct1.id), - ('group_id', '=', self.production2.procurement_group_id.id)]) - self.assertEquals(len(procurement_subproduct1_2), 0) + group = self.procurement_model.search( + [('name', '=', self.production2.name)]) + self.assertEquals(len(group), 1) self.assertEquals(self.production2.availability, 'assigned') self.production2.do_unreserve() - self.assertEquals(self.subproduct1.virtual_available, 0) self.production.action_assign() # We check if first MO is able to assign it self even if it has # previously generate procurements, it would not be the case in the # other mode (without mrp_mto_mts_reservable_stock on warehouse) - self.assertEquals(self.production.availability, 'assigned') + self.assertEquals(self.production.availability, 'partially_available') self.assertEquals(self.subproduct1.virtual_available, 0) @@ -126,33 +115,38 @@ def test_manufacture_with_reservable_stock(self): # Create MO and check it create sub assemblie MO. self.production.action_assign() self.assertEqual(self.production.state, 'confirmed') + mo = self.production_model.search( + [('origin', 'ilike', self.production.name)]) + self.assertEqual(mo.product_qty, 3) + mo.action_assign() + self.assertEqual(mo.state, 'confirmed') + wizard_obj = self.env['mrp.product.produce'] + default_fields = ['lot_id', 'product_id', 'product_uom_id', + 'product_tracking', 'consume_line_ids', + 'production_id', 'product_qty', 'serial'] + wizard_vals = wizard_obj.with_context(active_id=mo.id).\ + default_get(default_fields) + wizard = wizard_obj.create(wizard_vals) + wizard.do_produce() + self.assertEqual(len(mo), 1) + mo.button_mark_done() + self.assertEqual(mo.state, 'done') + self.assertEquals(self.subproduct1.qty_available, 5) - procurement_sub1 = self.env['procurement.order'].search( - [('product_id', '=', self.subproduct1.id), - ('move_dest_id', 'in', self.production.move_raw_ids.ids)]) - self.assertEquals(len(procurement_sub1), 1) - - procurement_sub2 = self.env['procurement.order'].search( - [('product_id', '=', self.subproduct2.id), - ('move_dest_id', 'in', self.production.move_raw_ids.ids)]) - self.assertEquals(len(procurement_sub2), 0) - - production_sub1 = procurement_sub1.production_id - self.assertEqual(production_sub1.product_qty, 3) - production_sub1.action_assign() - self.assertEqual(production_sub1.availability, 'assigned') + self.production.action_assign() + self.assertEqual(self.production.state, 'confirmed') wizard_obj = self.env['mrp.product.produce'] default_fields = ['lot_id', 'product_id', 'product_uom_id', 'product_tracking', 'consume_line_ids', 'production_id', 'product_qty', 'serial'] - wizard_vals = wizard_obj.with_context(active_id=production_sub1.id).\ + wizard_vals = wizard_obj.with_context(active_id=self.production.id).\ default_get(default_fields) wizard = wizard_obj.create(wizard_vals) wizard.do_produce() - self.assertTrue(production_sub1.check_to_done) - self.assertEquals(self.subproduct1.qty_available, 2) - production_sub1.button_mark_done() - self.assertEquals(self.subproduct1.qty_available, 5) - self.assertEqual(self.production.availability, 'assigned') + + self.assertTrue(self.production.check_to_done) + self.production.button_mark_done() + self.assertEqual(self.production.state, 'done') + self.assertEquals(self.subproduct2.qty_available, 2) diff --git a/mrp_mto_with_stock/views/product_template_view.xml b/mrp_mto_with_stock/views/product_template_view.xml index cbe221f8f9b..5eff34d8997 100644 --- a/mrp_mto_with_stock/views/product_template_view.xml +++ b/mrp_mto_with_stock/views/product_template_view.xml @@ -1,4 +1,4 @@ - + diff --git a/mrp_mto_with_stock/views/stock_warehouse.xml b/mrp_mto_with_stock/views/stock_warehouse.xml index 68a4266ce24..6775e1e8044 100644 --- a/mrp_mto_with_stock/views/stock_warehouse.xml +++ b/mrp_mto_with_stock/views/stock_warehouse.xml @@ -1,4 +1,4 @@ - +