diff --git a/mrp_multi_level/README.rst b/mrp_multi_level/README.rst index 1b79418f4..f98997dc3 100644 --- a/mrp_multi_level/README.rst +++ b/mrp_multi_level/README.rst @@ -78,15 +78,16 @@ To launch replenishment orders (moves, purchases, production orders...): hand side gears in any record. #. On the wizard, check everything is ok and click *Execute*. -Known issues / Roadmap -====================== - -* The functionality related to field *Nbr. Days* in products is not - functional for the time being. Please, stay tuned to future updates. - Changelog ========= +11.0.2.1.0 (2019-04-02) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [IMP] Implement *Nbr. Days* functionality to be able to group demand when + generating supply proposals. + (`#345 `_): + 11.0.2.0.0 (2018-11-20) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mrp_multi_level/__manifest__.py b/mrp_multi_level/__manifest__.py index f32d72a20..be4db8065 100644 --- a/mrp_multi_level/__manifest__.py +++ b/mrp_multi_level/__manifest__.py @@ -1,9 +1,9 @@ # Copyright 2016 Ucamco - Wim Audenaert -# Copyright 2016-18 Eficent Business and IT Consulting Services S.L. +# Copyright 2016-19 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { 'name': 'MRP Multi Level', - 'version': '11.0.2.0.0', + 'version': '11.0.2.1.0', 'development_status': 'Beta', 'license': 'AGPL-3', 'author': 'Ucamco, ' diff --git a/mrp_multi_level/readme/HISTORY.rst b/mrp_multi_level/readme/HISTORY.rst index 03ff5468e..5bfd1ed95 100644 --- a/mrp_multi_level/readme/HISTORY.rst +++ b/mrp_multi_level/readme/HISTORY.rst @@ -1,3 +1,10 @@ +11.0.2.1.0 (2019-04-02) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [IMP] Implement *Nbr. Days* functionality to be able to group demand when + generating supply proposals. + (`#345 `_): + 11.0.2.0.0 (2018-11-20) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mrp_multi_level/readme/ROADMAP.rst b/mrp_multi_level/readme/ROADMAP.rst deleted file mode 100644 index b3ae9a0e8..000000000 --- a/mrp_multi_level/readme/ROADMAP.rst +++ /dev/null @@ -1,2 +0,0 @@ -* The functionality related to field *Nbr. Days* in products is not - functional for the time being. Please, stay tuned to future updates. diff --git a/mrp_multi_level/static/description/index.html b/mrp_multi_level/static/description/index.html index 8fc23d952..a584a53aa 100644 --- a/mrp_multi_level/static/description/index.html +++ b/mrp_multi_level/static/description/index.html @@ -386,40 +386,40 @@ and explodes this down to the lowest level.

Table of contents

-

Configuration

+

Configuration

-

MRP Areas

+

MRP Areas

  • Go to Manufacturing > Configuration > MRP Areas and define or edit any existing area. You can specify the working hours for every area.
-

Product MRP Area Parameters

+

Product MRP Area Parameters

  • Go to Manufacturing > Master Data > Product MRP Area Parameters and set the MRP parameters for a given product and area.
  • @@ -427,7 +427,7 @@ the MRP parameters for a given product and area.
-

Usage

+

Usage

To manually run the MRP scheduler:

  1. Go to Manufacturing > Operations > Run MRP Multi Level.
  2. @@ -442,17 +442,18 @@ hand side gears in any record.
  3. On the wizard, check everything is ok and click Execute.
-
-

Known issues / Roadmap

+
+

Changelog

+
+

11.0.2.1.0 (2019-04-02)

    -
  • The functionality related to field Nbr. Days in products is not -functional for the time being. Please, stay tuned to future updates.
  • +
  • [IMP] Implement Nbr. Days functionality to be able to group demand when +generating supply proposals. +(#345):
-
-

Changelog

-
-

11.0.2.0.0 (2018-11-20)

+
+

11.0.2.0.0 (2018-11-20)

  • [REW] Refactor MRP Area. (#322):
      @@ -464,15 +465,15 @@ different areas.
-
-

11.0.1.1.0 (2018-08-30)

+
+

11.0.1.1.0 (2018-08-30)

  • [FIX] Consider Qty Multiple on product to propose the quantity to procure. (#297)
-
-

11.0.1.0.1 (2018-08-03)

+
+

11.0.1.0.1 (2018-08-03)

  • [FIX] User and system locales doesn’t break MRP calculation. (#290)
  • @@ -481,15 +482,15 @@ as a related on MRP Areas. (#290)
-
-

Bug Tracker

+

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 smashing it by providing a detailed and welcomed @@ -497,16 +498,16 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Ucamco
  • Eficent
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py index c417a1fe1..0dc2cf1b0 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -1,4 +1,4 @@ -# Copyright 2018 Eficent Business and IT Consulting Services S.L. +# Copyright 2018-19 Eficent Business and IT Consulting Services S.L. # (http://www.eficent.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). @@ -17,6 +17,8 @@ class TestMrpMultiLevel(SavepointCase): cls.mo_obj = cls.env['mrp.production'] cls.po_obj = cls.env['purchase.order'] cls.product_obj = cls.env['product.product'] + cls.loc_obj = cls.env['stock.location'] + cls.mrp_area_obj = cls.env['mrp.area'] cls.product_mrp_area_obj = cls.env['product.mrp.area'] cls.partner_obj = cls.env['res.partner'] cls.stock_picking_obj = cls.env['stock.picking'] @@ -45,6 +47,18 @@ class TestMrpMultiLevel(SavepointCase): # Partner: vendor1 = cls.partner_obj.create({'name': 'Vendor 1'}) + # Create secondary location and MRP Area: + cls.sec_loc = cls.loc_obj.create({ + 'name': 'Test location', + 'usage': 'internal', + 'location_id': cls.wh.view_location_id.id, + }) + cls.secondary_area = cls.mrp_area_obj.create({ + 'name': 'Test', + 'warehouse_id': cls.wh.id, + 'location_id': cls.sec_loc.id, + }) + # Create products: route_buy = cls.env.ref('purchase.route_warehouse0_buy').id cls.prod_test = cls.product_obj.create({ @@ -59,6 +73,12 @@ class TestMrpMultiLevel(SavepointCase): 'product_id': cls.prod_test.id, 'mrp_area_id': cls.mrp_area.id, }) + # Parameters in secondary area with nbr_days set. + cls.product_mrp_area_obj.create({ + 'product_id': cls.prod_test.id, + 'mrp_area_id': cls.secondary_area.id, + 'mrp_nbr_days': 7, + }) cls.prod_min = cls.product_obj.create({ 'name': 'Product with minimum order qty', 'type': 'product', @@ -239,6 +259,8 @@ class TestMrpMultiLevel(SavepointCase): qty += 70.0 cls._create_demand_estimate( cls.prod_test, cls.stock_location, dr, qty) + cls._create_demand_estimate( + cls.prod_test, cls.sec_loc, dr, qty) cls.mrp_multi_level_wiz.create({}).run_mrp_multi_level() @@ -425,11 +447,13 @@ class TestMrpMultiLevel(SavepointCase): def test_06_demand_estimates(self): """Tests demand estimates integration.""" - estimates = self.estimate_obj.search( - [('product_id', '=', self.prod_test.id)]) + estimates = self.estimate_obj.search([ + ('product_id', '=', self.prod_test.id), + ('location_id', '=', self.stock_location.id)]) self.assertEqual(len(estimates), 3) moves = self.mrp_move_obj.search([ ('product_id', '=', self.prod_test.id), + ('mrp_area_id', '=', self.mrp_area.id), ]) # 3 weeks - 3 days in the past = 18 days of valid estimates: moves_from_estimates = moves.filtered(lambda m: m.mrp_type == 'd') @@ -440,6 +464,9 @@ class TestMrpMultiLevel(SavepointCase): self.assertIn(-50.0, quantities) # 350 a week => 50.0 dayly: actions = moves.filtered(lambda m: m.mrp_action == 'po') self.assertEqual(len(actions), 18) + inventories = self.mrp_inventory_obj.search([ + ('mrp_area_id', '=', self.secondary_area.id)]) + self.assertEqual(len(inventories), 18) def test_07_procure_mo(self): """Test procurement wizard with MOs.""" @@ -483,5 +510,29 @@ class TestMrpMultiLevel(SavepointCase): ('product_mrp_area_id.product_id', '=', self.prod_multiple.id)]) self.assertEqual(mrp_inv_multiple.to_procure, 125) + def test_09_group_demand(self): + """Test demand grouping functionality, `nbr_days`.""" + estimates = self.estimate_obj.search([ + ('product_id', '=', self.prod_test.id), + ('location_id', '=', self.sec_loc.id)]) + self.assertEqual(len(estimates), 3) + moves = self.mrp_move_obj.search([ + ('product_id', '=', self.prod_test.id), + ('mrp_area_id', '=', self.secondary_area.id), + ]) + # 3 weeks - 3 days in the past = 18 days of valid estimates: + moves_from_estimates = moves.filtered(lambda m: m.mrp_type == 'd') + self.assertEqual(len(moves_from_estimates), 18) + # 18 days of demand / 7 nbr_days = 2.57 => 3 supply moves expected. + supply_moves = moves.filtered(lambda m: m.mrp_type == 's') + self.assertEqual(len(supply_moves), 3) + quantities = supply_moves.mapped('mrp_qty') + week_1_expected = sum(moves_from_estimates[0:7].mapped('mrp_qty')) + self.assertIn(abs(week_1_expected), quantities) + week_2_expected = sum(moves_from_estimates[7:14].mapped('mrp_qty')) + self.assertIn(abs(week_2_expected), quantities) + week_3_expected = sum(moves_from_estimates[14:].mapped('mrp_qty')) + self.assertIn(abs(week_3_expected), quantities) + # TODO: test procure wizard: pos, multiple... # TODO: test multiple destination IDS:... diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py index 2143d0ca5..67b3b1f8b 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -1,6 +1,7 @@ -# © 2016 Ucamco - Wim Audenaert -# Copyright 2016-18 Eficent Business and IT Consulting Services S.L. +# Copyright 2016 Ucamco - Wim Audenaert +# Copyright 2016-19 Eficent Business and IT Consulting Services S.L. # - Jordi Ballester Alomar +# - Lois Rilo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from odoo import api, fields, models, exceptions, _ @@ -507,64 +508,48 @@ class MultiLevelMrp(models.TransientModel): last_date = None last_qty = 0.00 onhand = product_mrp_area.qty_available - move_ids = [] - for move in product_mrp_area.mrp_move_ids: - move_ids.append(move.id) - for move_id in move_ids: - move_rec = self.env['mrp.move'].search( - [('id', '=', move_id)]) - for move in move_rec: - if move.mrp_action == 'none': - if last_date is not None: - if datetime.date( - datetime.strptime( - move.mrp_date, '%Y-%m-%d')) \ - > last_date+timedelta( - days=product_mrp_area.mrp_nbr_days): - if (onhand + last_qty + move.mrp_qty) \ - < product_mrp_area.mrp_minimum_stock \ - or (onhand + last_qty) \ - < product_mrp_area.mrp_minimum_stock: - name = 'Grouped Demand for %d Days' % \ - product_mrp_area.mrp_nbr_days - qtytoorder = \ - product_mrp_area.mrp_minimum_stock - \ - product_mrp_area - last_qty - cm = self.create_move( - product_mrp_area_id=product_mrp_area, - mrp_date=last_date, - mrp_qty=qtytoorder, - name=name) - qty_ordered = cm['qty_ordered'] - onhand = onhand + last_qty + qty_ordered - last_date = None - last_qty = 0.00 - nbr_create += 1 - if (onhand + last_qty + move.mrp_qty) < \ - product_mrp_area.mrp_minimum_stock or \ - (onhand + last_qty) < \ - product_mrp_area.mrp_minimum_stock: - if last_date is None: - last_date = datetime.date( - datetime.strptime(move.mrp_date, - '%Y-%m-%d')) - last_qty = move.mrp_qty - else: - last_qty = last_qty + move.mrp_qty - else: - last_date = datetime.date( - datetime.strptime(move.mrp_date, - '%Y-%m-%d')) - onhand = onhand + move.mrp_qty + grouping_delta = product_mrp_area.mrp_nbr_days + for move in product_mrp_area.mrp_move_ids.filtered( + lambda m: m.mrp_action == 'none'): + if last_date and ( + fields.Date.from_string(move.mrp_date) + >= last_date + timedelta(days=grouping_delta)) and ( + (onhand + last_qty + move.mrp_qty) + < product_mrp_area.mrp_minimum_stock + or (onhand + last_qty) + < product_mrp_area.mrp_minimum_stock): + name = 'Grouped Demand for %d Days' % grouping_delta + qtytoorder = product_mrp_area.mrp_minimum_stock - last_qty + cm = self.create_move( + product_mrp_area_id=product_mrp_area, + mrp_date=last_date, + mrp_qty=qtytoorder, + name=name) + qty_ordered = cm.get('qty_ordered', 0.0) + onhand = onhand + last_qty + qty_ordered + last_date = None + last_qty = 0.00 + nbr_create += 1 + if (onhand + last_qty + move.mrp_qty) < \ + product_mrp_area.mrp_minimum_stock or \ + (onhand + last_qty) < \ + product_mrp_area.mrp_minimum_stock: + if not last_date: + last_date = fields.Date.from_string(move.mrp_date) + last_qty = move.mrp_qty + else: + last_qty += move.mrp_qty + else: + last_date = fields.Date.from_string(move.mrp_date) + onhand += move.mrp_qty - if last_date is not None and last_qty != 0.00: - name = 'Grouped Demand for %d Days' % \ - (product_mrp_area.mrp_nbr_days, ) + if last_date and last_qty != 0.00: + name = 'Grouped Demand for %d Days' % grouping_delta qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty cm = self.create_move( product_mrp_area_id=product_mrp_area, mrp_date=last_date, mrp_qty=qtytoorder, name=name) - qty_ordered = cm['qty_ordered'] + qty_ordered = cm.get('qty_ordered', 0.0) onhand += qty_ordered nbr_create += 1 return nbr_create @@ -605,7 +590,6 @@ class MultiLevelMrp(models.TransientModel): else: onhand += move.mrp_qty else: - # TODO: review this nbr_create = self._init_mrp_move_grouped_demand( nbr_create, product_mrp_area)