diff --git a/mrp_multi_level/README.rst b/mrp_multi_level/README.rst index f98997dc3..1886afb25 100644 --- a/mrp_multi_level/README.rst +++ b/mrp_multi_level/README.rst @@ -81,6 +81,12 @@ To launch replenishment orders (moves, purchases, production orders...): Changelog ========= +11.0.2.2.0 (2019-05-02) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [IMP] Able to run MRP only for selected areas. + (`#360 `_): + 11.0.2.1.0 (2019-04-02) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mrp_multi_level/__manifest__.py b/mrp_multi_level/__manifest__.py index be4db8065..5d9252026 100644 --- a/mrp_multi_level/__manifest__.py +++ b/mrp_multi_level/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { 'name': 'MRP Multi Level', - 'version': '11.0.2.1.0', + 'version': '11.0.2.2.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 5bfd1ed95..0b86704df 100644 --- a/mrp_multi_level/readme/HISTORY.rst +++ b/mrp_multi_level/readme/HISTORY.rst @@ -1,3 +1,9 @@ +11.0.2.2.0 (2019-05-02) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [IMP] Able to run MRP only for selected areas. + (`#360 `_): + 11.0.2.1.0 (2019-04-02) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mrp_multi_level/static/description/index.html b/mrp_multi_level/static/description/index.html index a584a53aa..aa8f60935 100644 --- a/mrp_multi_level/static/description/index.html +++ b/mrp_multi_level/static/description/index.html @@ -386,40 +386,41 @@ 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 +428,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. @@ -443,17 +444,24 @@ hand side gears in any record.
-

Changelog

+

Changelog

-

11.0.2.1.0 (2019-04-02)

+

11.0.2.2.0 (2019-05-02)

+
    +
  • [IMP] Able to run MRP only for selected areas. +(#360):
  • +
+
+
+

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)

+
+

11.0.2.0.0 (2018-11-20)

  • [REW] Refactor MRP Area. (#322):
      @@ -465,15 +473,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)
  • @@ -482,15 +490,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 @@ -498,16 +506,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 0dc2cf1b0..09e00db97 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -21,6 +21,7 @@ class TestMrpMultiLevel(SavepointCase): 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.res_users = cls.env['res.users'] cls.stock_picking_obj = cls.env['stock.picking'] cls.estimate_obj = cls.env['stock.demand.estimate'] cls.mrp_multi_level_wiz = cls.env['mrp.multi.level'] @@ -34,6 +35,7 @@ class TestMrpMultiLevel(SavepointCase): cls.sf_2 = cls.env.ref('mrp_multi_level.product_product_sf_2') cls.pp_1 = cls.env.ref('mrp_multi_level.product_product_pp_1') cls.pp_2 = cls.env.ref('mrp_multi_level.product_product_pp_2') + cls.company = cls.env.ref('base.main_company') cls.mrp_area = cls.env.ref('mrp_multi_level.mrp_area_stock_wh0') cls.vendor = cls.env.ref('mrp_multi_level.res_partner_lazer_tech') cls.wh = cls.env.ref('stock.warehouse0') @@ -47,6 +49,16 @@ class TestMrpMultiLevel(SavepointCase): # Partner: vendor1 = cls.partner_obj.create({'name': 'Vendor 1'}) + # Create user: + group_mrp_manager = cls.env.ref('mrp.group_mrp_manager') + group_user = cls.env.ref('base.group_user') + group_stock_manager = cls.env.ref('stock.group_stock_manager') + cls.mrp_manager = cls._create_user( + 'Test User', + [group_mrp_manager, group_user, group_stock_manager], + cls.company, + ) + # Create secondary location and MRP Area: cls.sec_loc = cls.loc_obj.create({ 'name': 'Test location', @@ -274,6 +286,18 @@ class TestMrpMultiLevel(SavepointCase): 'date_range_id': date_range.id, }) + @classmethod + def _create_user(cls, login, groups, company): + user = cls.res_users.create({ + 'name': login, + 'login': login, + 'password': 'demo', + 'email': 'example@yourcompany.com', + 'company_id': company.id, + 'groups_id': [(6, 0, [group.id for group in groups])] + }) + return user + def test_01_mrp_levels(self): """Tests computation of MRP levels.""" self.assertEqual(self.fp_1.llc, 0) @@ -438,12 +462,6 @@ class TestMrpMultiLevel(SavepointCase): self.assertEqual(len(moves), 6) expected = [200.0, 290.0, 90.0, 0.0, 72.0, 0.0] self.assertEqual(moves.mapped('running_availability'), expected) - # Actions counters for PP-1: - # product_mrp_area = self.product_mrp_area_obj.search([ - # ('product_id', '=', self.pp_1.id) - # ]) # TODO - # self.assertEqual(product_mrp_area.nbr_mrp_actions, 3) # TODO - # self.assertEqual(product_mrp_area.nbr_mrp_actions_4w, 3) # TODO def test_06_demand_estimates(self): """Tests demand estimates integration.""" @@ -534,5 +552,19 @@ class TestMrpMultiLevel(SavepointCase): week_3_expected = sum(moves_from_estimates[14:].mapped('mrp_qty')) self.assertIn(abs(week_3_expected), quantities) + def test_10_isolated_mrp_area_run(self): + """Test running MRP for just one area.""" + self.mrp_multi_level_wiz.sudo(self.mrp_manager).create({ + 'mrp_area_ids': [(6, 0, self.secondary_area.ids)], + }).run_mrp_multi_level() + this = self.mrp_inventory_obj.search([ + ('mrp_area_id', '=', self.secondary_area.id)], limit=1) + self.assertTrue(this) + # Only recently exectued areas should have been created by test user: + self.assertEqual(this.create_uid, self.mrp_manager) + prev = self.mrp_inventory_obj.search([ + ('mrp_area_id', '!=', self.secondary_area.id)], limit=1) + self.assertNotEqual(this.create_uid, prev.create_uid) + # 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 67b3b1f8b..eccd80b8c 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -15,6 +15,12 @@ logger = logging.getLogger(__name__) class MultiLevelMrp(models.TransientModel): _name = 'mrp.multi.level' + mrp_area_ids = fields.Many2many( + comodel_name="mrp.area", + string="MRP Areas to run", + help="If empty, all areas will be computed.", + ) + # TODO: dates are not being correctly computed for supply... @api.model @@ -269,31 +275,36 @@ class MultiLevelMrp(models.TransientModel): mrpmove_id2 = self.env['mrp.move'].create(move_data) mrpmove_id.mrp_move_down_ids = [(4, mrpmove_id2.id)] values['qty_ordered'] = qty_ordered - log_msg = '%s' % qty_ordered - logger.info(log_msg) + log_msg = '[%s] %s: qty_ordered = %s' % ( + product_mrp_area_id.mrp_area_id.name, + product_mrp_area_id.product_id.default_code or + product_mrp_area_id.product_id.name, + qty_ordered, + ) + logger.debug(log_msg) return values @api.model - def _mrp_cleanup(self): - # Some part of the code with the new API is replaced by - # sql statements due to performance issues when the auditlog is - # installed - logger.info('START MRP CLEANUP') - self.env['mrp.move'].search([]).unlink() - self.env['mrp.inventory'].search([]).unlink() - logger.info('END MRP CLEANUP') + def _mrp_cleanup(self, mrp_areas): + logger.info('Start MRP Cleanup') + domain = [] + if mrp_areas: + domain += [('mrp_area_id', 'in', mrp_areas.ids)] + self.env['mrp.move'].search(domain).unlink() + self.env['mrp.inventory'].search(domain).unlink() + logger.info('End MRP Cleanup') return True @api.model def _low_level_code_calculation(self): - logger.info('START LOW LEVEL CODE CALCULATION') + logger.info('Start low level code calculation') counter = 999999 llc = 0 self.env['product.product'].search([]).write({'llc': llc}) products = self.env['product.product'].search([('llc', '=', llc)]) if products: counter = len(products) - log_msg = 'LOW LEVEL CODE 0 FINISHED - NBR PRODUCTS: %s' % counter + log_msg = 'Low level code 0 finished - Nbr. products: %s' % counter logger.info(log_msg) while counter: @@ -308,33 +319,38 @@ class MultiLevelMrp(models.TransientModel): products.write({'llc': llc}) products = self.env['product.product'].search([('llc', '=', llc)]) counter = len(products) - log_msg = 'LOW LEVEL CODE %s FINISHED - NBR PRODUCTS: %s' % ( + log_msg = 'Low level code %s finished - Nbr. products: %s' % ( llc, counter) logger.info(log_msg) mrp_lowest_llc = llc - logger.info('END LOW LEVEL CODE CALCULATION') + logger.info('End low level code calculation') return mrp_lowest_llc @api.model - def _adjust_mrp_applicable(self): + def _adjust_mrp_applicable(self, mrp_areas): """This method is meant to modify the products that are applicable to MRP Multi level calculation """ return True @api.model - def _calculate_mrp_applicable(self): - logger.info('CALCULATE MRP APPLICABLE') - self.env['product.mrp.area'].search([]).write( + def _calculate_mrp_applicable(self, mrp_areas): + logger.info('Start Calculate MRP Applicable') + domain = [] + if mrp_areas: + domain += [('mrp_area_id', 'in', mrp_areas.ids)] + self.env['product.mrp.area'].search(domain).write( {'mrp_applicable': False}) - self.env['product.mrp.area'].search([ - ('product_id.type', '=', 'product'), - ]).write({'mrp_applicable': True}) - self._adjust_mrp_applicable() - counter = self.env['product.mrp.area'].search([ - ('mrp_applicable', '=', True)], count=True) - log_msg = 'END CALCULATE MRP APPLICABLE: %s' % counter + domain += [('product_id.type', '=', 'product')] + self.env['product.mrp.area'].search(domain).write( + {'mrp_applicable': True}) + self._adjust_mrp_applicable(mrp_areas) + count_domain = [('mrp_applicable', '=', True)] + if mrp_areas: + count_domain += [('mrp_area_id', 'in', mrp_areas.ids)] + counter = self.env['product.mrp.area'].search(count_domain, count=True) + log_msg = 'End Calculate MRP Applicable: %s' % counter logger.info(log_msg) return True @@ -484,11 +500,14 @@ class MultiLevelMrp(models.TransientModel): return product_mrp_area.mrp_exclude @api.model - def _mrp_initialisation(self): - logger.info('START MRP INITIALISATION') - mrp_areas = self.env['mrp.area'].search([]) + def _mrp_initialisation(self, mrp_areas): + logger.info('Start MRP initialisation') + if not mrp_areas: + mrp_areas = self.env['mrp.area'].search([]) product_mrp_areas = self.env['product.mrp.area'].search([ - ('mrp_applicable', '=', True)]) + ('mrp_area_id', 'in', mrp_areas.ids), + ('mrp_applicable', '=', True), + ]) init_counter = 0 for mrp_area in mrp_areas: for product_mrp_area in product_mrp_areas.filtered( @@ -497,11 +516,11 @@ class MultiLevelMrp(models.TransientModel): product_mrp_area.product_id, mrp_area): continue init_counter += 1 - log_msg = 'MRP INIT: %s - %s ' % ( + log_msg = 'MRP Init: %s - %s ' % ( init_counter, product_mrp_area.display_name) logger.info(log_msg) self._init_mrp_move(product_mrp_area) - logger.info('END MRP INITIALISATION') + logger.info('End MRP initialisation') @api.model def _init_mrp_move_grouped_demand(self, nbr_create, product_mrp_area): @@ -555,11 +574,13 @@ class MultiLevelMrp(models.TransientModel): return nbr_create @api.model - def _mrp_calculation(self, mrp_lowest_llc): - logger.info('START MRP CALCULATION') + def _mrp_calculation(self, mrp_lowest_llc, mrp_areas): + logger.info('Start MRP calculation') product_mrp_area_obj = self.env['product.mrp.area'] counter = 0 - for mrp_area in self.env['mrp.area'].search([]): + if not mrp_areas: + mrp_areas = self.env['mrp.area'].search([]) + for mrp_area in mrp_areas: llc = 0 while mrp_lowest_llc > llc: product_mrp_areas = product_mrp_area_obj.search( @@ -606,11 +627,11 @@ class MultiLevelMrp(models.TransientModel): onhand += qty_ordered counter += 1 - log_msg = 'MRP CALCULATION LLC %s FINISHED - NBR PRODUCTS: %s' % ( + log_msg = 'MRP Calculation LLC %s Finished - Nbr. products: %s' % ( llc - 1, counter) logger.info(log_msg) - logger.info('END MRP CALCULATION') + logger.info('Enb MRP calculation') @api.model def _get_demand_groups(self, product_mrp_area): @@ -714,10 +735,12 @@ class MultiLevelMrp(models.TransientModel): self.env['mrp.inventory'].create(mrp_inventory_data) @api.model - def _mrp_final_process(self): - logger.info('START MRP FINAL PROCESS') - product_mrp_area_ids = self.env['product.mrp.area'].search([ - ('product_id.llc', '<', 9999)]) + def _mrp_final_process(self, mrp_areas): + logger.info('Start MRP final process') + domain = [('product_id.llc', '<', 9999)] + if mrp_areas: + domain += [('mrp_area_id', 'in', mrp_areas.ids)] + product_mrp_area_ids = self.env['product.mrp.area'].search(domain) for product_mrp_area in product_mrp_area_ids: # Build the time-phased inventory @@ -732,25 +755,13 @@ class MultiLevelMrp(models.TransientModel): for move in moves: qoh = qoh + move.mrp_qty move.running_availability = qoh - # TODO: Possible clean up needed here - # nbr_actions = product_mrp_area.mrp_move_ids.filtered( - # lambda m: m.mrp_action != 'none') - # horizon_4w = fields.Date.to_string( - # date.today() + timedelta(weeks=4)) - # nbr_actions_4w = nbr_actions.filtered( - # lambda m: m.mrp_action_date < horizon_4w) - # if nbr_actions: - # product_mrp_area.write({ - # 'nbr_mrp_actions': len(nbr_actions), - # 'nbr_mrp_actions_4w': len(nbr_actions_4w), - # }) - logger.info('END MRP FINAL PROCESS') + logger.info('End MRP final process') @api.multi def run_mrp_multi_level(self): - self._mrp_cleanup() + self._mrp_cleanup(self.mrp_area_ids) mrp_lowest_llc = self._low_level_code_calculation() - self._calculate_mrp_applicable() - self._mrp_initialisation() - self._mrp_calculation(mrp_lowest_llc) - self._mrp_final_process() + self._calculate_mrp_applicable(self.mrp_area_ids) + self._mrp_initialisation(self.mrp_area_ids) + self._mrp_calculation(mrp_lowest_llc, self.mrp_area_ids) + self._mrp_final_process(self.mrp_area_ids) diff --git a/mrp_multi_level/wizards/mrp_multi_level_views.xml b/mrp_multi_level/wizards/mrp_multi_level_views.xml index f7272199e..3685ab597 100644 --- a/mrp_multi_level/wizards/mrp_multi_level_views.xml +++ b/mrp_multi_level/wizards/mrp_multi_level_views.xml @@ -6,6 +6,9 @@ mrp.multi.level

+ + +