From cad67a0231da28b0e5771c349a051b114474c6e4 Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Thu, 21 Jun 2018 11:25:23 +0200 Subject: [PATCH] [11.0][REW] multi_level_mrp: substitute mrp forecast with stock demand estimates --- multi_level_mrp/__manifest__.py | 2 +- multi_level_mrp/models/__init__.py | 1 - multi_level_mrp/models/mrp_forecast.py | 197 ------------------ multi_level_mrp/tests/test_multi_level_mrp.py | 75 ++++++- multi_level_mrp/views/mrp_forecast_view.xml | 74 ------- multi_level_mrp/views/mrp_menuitem.xml | 6 - multi_level_mrp/wizards/multi_level_mrp.py | 53 ++--- 7 files changed, 101 insertions(+), 307 deletions(-) delete mode 100644 multi_level_mrp/models/mrp_forecast.py delete mode 100644 multi_level_mrp/views/mrp_forecast_view.xml diff --git a/multi_level_mrp/__manifest__.py b/multi_level_mrp/__manifest__.py index 6d009ae06..dcd161cb9 100644 --- a/multi_level_mrp/__manifest__.py +++ b/multi_level_mrp/__manifest__.py @@ -14,11 +14,11 @@ 'mrp', 'stock', 'purchase', + 'stock_demand_estimate', ], 'data': [ 'security/multi_level_mrp_security.xml', 'security/ir.model.access.csv', - 'views/mrp_forecast_view.xml', 'views/mrp_area_view.xml', 'views/product_view.xml', 'views/stock_location_view.xml', diff --git a/multi_level_mrp/models/__init__.py b/multi_level_mrp/models/__init__.py index ca00b054e..353d6e3ed 100644 --- a/multi_level_mrp/models/__init__.py +++ b/multi_level_mrp/models/__init__.py @@ -1,6 +1,5 @@ from . import mrp_area from . import stock_location -from . import mrp_forecast from . import product from . import mrp_product from . import mrp_move diff --git a/multi_level_mrp/models/mrp_forecast.py b/multi_level_mrp/models/mrp_forecast.py deleted file mode 100644 index b7787d022..000000000 --- a/multi_level_mrp/models/mrp_forecast.py +++ /dev/null @@ -1,197 +0,0 @@ -# © 2016 Ucamco - Wim Audenaert -# © 2016 Eficent Business and IT Consulting Services S.L. -# - Jordi Ballester Alomar -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -from odoo import api, fields, models -from datetime import date, datetime - - -class MrpForecastForecast(models.Model): - _name = 'mrp.forecast.forecast' - _order = 'forecast_product_id, date' - - date = fields.Date('Date') - forecast_product_id = fields.Many2one('mrp.forecast.product', 'Product', - select=True) - name = fields.Char('Description') - qty_forecast = fields.Float('Quantity') - - -class MrpForecastProduct(models.Model): - _name = 'mrp.forecast.product' - # TODO: adapt to demand_estimate?? or at least to date_range?? - - @api.one - @api.depends('product_id') - def _function_name(self): - if self.product_id: - self.name = "[%s] %s" % (self.product_id.default_code, - self.product_id.name, ) - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_m0(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - fcmonth = (datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')).year * 100) + \ - datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')).month - tmonth = (date.today().year * 100) + date.today().month - if not(datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')) < date.today()) \ - and fcmonth == tmonth: - qty += fc.qty_forecast - self.qty_forecast_m0 = qty - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_m1(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - fcmonth = (datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')).year * 100) + datetime.date( - datetime.strptime(fc.date, '%Y-%m-%d')).month - tdyear = date.today().year - tdmonth = date.today().month + 1 - if tdmonth > 12: - tdmonth -= 12 - tdyear += 1 - tmonth = (tdyear * 100) + tdmonth - if fcmonth == tmonth: - qty += fc.qty_forecast - self.qty_forecast_m1 = qty - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_m2(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - fcmonth = (datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')).year * 100) + datetime.date( - datetime.strptime(fc.date, '%Y-%m-%d')).month - tdyear = date.today().year - tdmonth = date.today().month + 2 - if tdmonth > 12: - tdmonth -= 12 - tdyear += 1 - tmonth = (tdyear * 100) + tdmonth - if fcmonth == tmonth: - qty += fc.qty_forecast - self.qty_forecast_m2 = qty - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_m3(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - fcmonth = (datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')).year * 100) + datetime.date( - datetime.strptime(fc.date, '%Y-%m-%d')).month - tdyear = date.today().year - tdmonth = date.today().month + 3 - if tdmonth > 12: - tdmonth -= 12 - tdyear += 1 - tmonth = (tdyear * 100) + tdmonth - if fcmonth == tmonth: - qty += fc.qty_forecast - self.qty_forecast_m3 = qty - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_m4(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - fcmonth = (datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')).year * 100) + datetime.date( - datetime.strptime(fc.date, '%Y-%m-%d')).month - tdyear = date.today().year - tdmonth = date.today().month + 4 - if tdmonth > 12: - tdmonth -= 12 - tdyear += 1 - tmonth = (tdyear * 100) + tdmonth - if fcmonth == tmonth: - qty += fc.qty_forecast - self.qty_forecast_m4 = qty - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_m5(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - fcmonth = (datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')).year * 100) + datetime.date( - datetime.strptime(fc.date, '%Y-%m-%d')).month - tdyear = date.today().year - tdmonth = date.today().month + 5 - if tdmonth > 12: - tdmonth -= 12 - tdyear += 1 - tmonth = (tdyear * 100) + tdmonth - if fcmonth == tmonth: - qty += fc.qty_forecast - self.qty_forecast_m5 = qty - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_m6(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - fcmonth = (datetime.date(datetime.strptime( - fc.date, '%Y-%m-%d')).year * 100) + datetime.date( - datetime.strptime(fc.date, '%Y-%m-%d')).month - tdyear = date.today().year - tdmonth = date.today().month + 6 - if tdmonth > 12: - tdmonth -= 12 - tdyear += 1 - tmonth = (tdyear * 100) + tdmonth - if fcmonth == tmonth: - qty += fc.qty_forecast - self.qty_forecast_m6 = qty - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_past(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - if datetime.date( - datetime.strptime(fc.date, '%Y-%m-%d')) < date.today(): - qty += fc.qty_forecast - self.qty_forecast_past = qty - - @api.one - @api.depends('mrp_forecast_ids') - def _function_forecast_total(self): - qty = 0.00 - for fc in self.mrp_forecast_ids: - qty += fc.qty_forecast - self.qty_forecast_total = qty - - mrp_forecast_ids = fields.One2many('mrp.forecast.forecast', - 'forecast_product_id', - 'Forecast') - name = fields.Char(compute='_function_name', string='Description') - product_id = fields.Many2one('product.product', 'Product', index=True) - mrp_area_id = fields.Many2one('mrp.area', 'MRP Area') - qty_forecast_m0 = fields.Float(compute='_function_forecast_m0', - string='This Month Forecast') - qty_forecast_m1 = fields.Float(compute='_function_forecast_m1', - string='Month+1 Forecast') - qty_forecast_m2 = fields.Float(compute='_function_forecast_m2', - string='Month+2 Forecast') - qty_forecast_m3 = fields.Float(compute='_function_forecast_m3', - string='Month+3 Forecast') - qty_forecast_m4 = fields.Float(compute='_function_forecast_m4', - string='Month+4 Forecast') - qty_forecast_m5 = fields.Float(compute='_function_forecast_m5', - string='Month+5 Forecast') - qty_forecast_m6 = fields.Float(compute='_function_forecast_m6', - string='Month+6 Forecast') - qty_forecast_past = fields.Float(compute='_function_forecast_past', - string='Past Forecast') - qty_forecast_total = fields.Float(compute='_function_forecast_total', - string='Total Forecast') diff --git a/multi_level_mrp/tests/test_multi_level_mrp.py b/multi_level_mrp/tests/test_multi_level_mrp.py index 12ff58d71..44794bb4e 100644 --- a/multi_level_mrp/tests/test_multi_level_mrp.py +++ b/multi_level_mrp/tests/test_multi_level_mrp.py @@ -6,6 +6,7 @@ from datetime import date, datetime, timedelta from odoo.tests.common import SavepointCase from odoo import fields +from dateutil.rrule import WEEKLY class TestMultiLevelMRP(SavepointCase): @@ -15,7 +16,10 @@ class TestMultiLevelMRP(SavepointCase): super(TestMultiLevelMRP, cls).setUpClass() cls.mo_obj = cls.env['mrp.production'] cls.po_obj = cls.env['purchase.order'] + cls.product_obj = cls.env['product.product'] + cls.partner_obj = cls.env['res.partner'] cls.stock_picking_obj = cls.env['stock.picking'] + cls.estimate_obj = cls.env['stock.demand.estimate'] cls.multi_level_mrp_wiz = cls.env['multi.level.mrp'] cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure'] cls.mrp_inventory_obj = cls.env['mrp.inventory'] @@ -34,6 +38,20 @@ class TestMultiLevelMRP(SavepointCase): cls.customer_location = cls.env.ref( 'stock.stock_location_customers') + # Partner: + vendor1 = cls.partner_obj.create({'name': 'Vendor 1'}) + + # Create products: + route_buy = cls.env.ref('purchase.route_warehouse0_buy').id + cls.prod_test = cls.product_obj.create({ + 'name': 'Test Top Seller', + 'type': 'product', + 'list_price': 150.0, + 'produce_delay': 5.0, + 'route_ids': [(6, 0, [route_buy])], + 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 20.0})], + }) + # Create test picking: date_move = datetime.today() + timedelta(days=7) cls.picking_1 = cls.stock_picking_obj.create({ @@ -90,7 +108,6 @@ class TestMultiLevelMRP(SavepointCase): 'product_uom_id': cls.fp_2.uom_id.id, 'date_planned_start': date_mo, }) - cls.multi_level_mrp_wiz.create({}).run_multi_level_mrp() # Dates (Strings): today = datetime.today() @@ -102,6 +119,42 @@ class TestMultiLevelMRP(SavepointCase): cls.date_9 = fields.Date.to_string(today + timedelta(days=9)) cls.date_10 = fields.Date.to_string(today + timedelta(days=10)) + # Create Date Ranges: + cls.dr_type = cls.env['date.range.type'].create({ + 'name': 'Weeks', + 'company_id': False, + 'allow_overlap': False, + }) + generator = cls.env['date.range.generator'].create({ + 'date_start': today - timedelta(days=3), + 'name_prefix': 'W-', + 'type_id': cls.dr_type.id, + 'duration_count': 1, + 'unit_of_time': WEEKLY, + 'count': 3}) + generator.action_apply() + + # Create Demand Estimates: + ranges = cls.env['date.range'].search( + [('type_id', '=', cls.dr_type.id)]) + qty = 140.0 + for dr in ranges: + qty += 70.0 + cls._create_demand_estimate( + cls.prod_test, cls.stock_location, dr, qty) + + cls.multi_level_mrp_wiz.create({}).run_multi_level_mrp() + + @classmethod + def _create_demand_estimate(cls, product, location, date_range, qty): + cls.estimate_obj.create({ + 'product_id': product.id, + 'location_id': location.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': qty, + 'date_range_id': date_range.id, + }) + def test_01_mrp_levels(self): """Tests computation of MRP levels.""" self.assertEqual(self.fp_1.llc, 0) @@ -271,7 +324,25 @@ class TestMultiLevelMRP(SavepointCase): self.assertEqual(mrp_product.nbr_mrp_actions, 3) self.assertEqual(mrp_product.nbr_mrp_actions_4w, 3) - def test_06_procure_mo(self): + def test_06_demand_estimates(self): + """Tests demand estimates integration.""" + estimates = self.estimate_obj.search( + [('product_id', '=', self.prod_test.id)]) + self.assertEqual(len(estimates), 3) + moves = self.mrp_move_obj.search([ + ('product_id', '=', self.prod_test.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) + quantities = moves_from_estimates.mapped('mrp_qty') + self.assertIn(-30.0, quantities) # 210 a week => 30.0 dayly: + self.assertIn(-40.0, quantities) # 280 a week => 40.0 dayly: + 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) + + def test_07_procure_mo(self): """Test procurement wizard with MOs.""" mos = self.mo_obj.search([ ('product_id', '=', self.fp_1.id)]) diff --git a/multi_level_mrp/views/mrp_forecast_view.xml b/multi_level_mrp/views/mrp_forecast_view.xml deleted file mode 100644 index 1b61a27b8..000000000 --- a/multi_level_mrp/views/mrp_forecast_view.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - mrp.forecast.tree - mrp.forecast.product - form - - - - - - - - - - - - - - - - - - - mrp.forecast.form - mrp.forecast.product - form - -
- - - - - - - - - - - - - - - - -
-
-
- - - - MRP Forecast - mrp.forecast.product - ir.actions.act_window - form - tree,form - - - - - - form - - - - - - tree - - - - -
diff --git a/multi_level_mrp/views/mrp_menuitem.xml b/multi_level_mrp/views/mrp_menuitem.xml index 6a715625b..8a3947fca 100644 --- a/multi_level_mrp/views/mrp_menuitem.xml +++ b/multi_level_mrp/views/mrp_menuitem.xml @@ -30,10 +30,4 @@ parent="menu_mrp_mrp" sequence="40"/> - - diff --git a/multi_level_mrp/wizards/multi_level_mrp.py b/multi_level_mrp/wizards/multi_level_mrp.py index 77b9bae5a..a7e83fb3a 100644 --- a/multi_level_mrp/wizards/multi_level_mrp.py +++ b/multi_level_mrp/wizards/multi_level_mrp.py @@ -40,28 +40,24 @@ class MultiLevelMrp(models.TransientModel): } @api.model - def _prepare_mrp_move_data_from_forecast(self, fc, fc_id, mrpproduct): + def _prepare_mrp_move_data_from_forecast( + self, estimate, mrp_product, date): mrp_type = 'd' origin = 'fc' - mrp_date = date.today() - if datetime.date(datetime.strptime(fc_id.date, - '%Y-%m-%d')) > date.today(): - mrp_date = datetime.date(datetime.strptime( - fc_id.date, '%Y-%m-%d')) return { - 'mrp_area_id': fc.mrp_area_id.id, - 'product_id': fc.product_id.id, - 'mrp_product_id': mrpproduct.id, + 'mrp_area_id': mrp_product.mrp_area_id.id, + 'product_id': mrp_product.product_id.id, + 'mrp_product_id': mrp_product.id, 'production_id': None, 'purchase_order_id': None, 'purchase_line_id': None, 'sale_order_id': None, 'sale_line_id': None, 'stock_move_id': None, - 'mrp_qty': -fc_id.qty_forecast, - 'current_qty': -fc_id.qty_forecast, - 'mrp_date': mrp_date, - 'current_date': mrp_date, + 'mrp_qty': -estimate.daily_qty, + 'current_qty': -estimate.daily_qty, + 'mrp_date': date, + 'current_date': date, 'mrp_action': 'none', 'mrp_type': mrp_type, 'mrp_processed': False, @@ -379,13 +375,6 @@ class MultiLevelMrp(models.TransientModel): AND llc > 0;""" self.env.cr.execute(sql_stat) - sql_stat = """ - UPDATE product_product - SET mrp_applicable=True - FROM mrp_forecast_product - WHERE product_product.id = mrp_forecast_product.product_id;""" - self.env.cr.execute(sql_stat) - # self.env.cr.commit() counter = 0 sql_stat = """ @@ -408,15 +397,27 @@ class MultiLevelMrp(models.TransientModel): @api.model def _init_mrp_move_from_forecast(self, mrp_product): - forecast = self.env['mrp.forecast.product'].search( - [('product_id', '=', mrp_product.product_id.id), - ('mrp_area_id', '=', mrp_product.mrp_area_id.id)]) - for fc in forecast: - for fc_id in fc.mrp_forecast_ids: + locations = self.env['stock.location'].search( + [('id', 'child_of', mrp_product.mrp_area_id.location_id.id)]) + today = fields.Date.today() + estimates = self.env['stock.demand.estimate'].search([ + ('product_id', '=', mrp_product.product_id.id), + ('location_id', 'in', locations.ids), + ('date_range_id.date_end', '>=', today) + ]) + for rec in estimates: + start = rec.date_range_id.date_start + if start < today: + start = today + mrp_date = fields.Date.from_string(start) + date_end = fields.Date.from_string(rec.date_range_id.date_end) + delta = timedelta(days=1) + while mrp_date <= date_end: mrp_move_data = \ self._prepare_mrp_move_data_from_forecast( - fc, fc_id, mrp_product) + rec, mrp_product, mrp_date) self.env['mrp.move'].create(mrp_move_data) + mrp_date += delta return True # TODO: move this methods to mrp_product?? to be able to show moves with an action