From 6ac8eab9bd1beaf09f4cbed338444d50f5f35dbf Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Tue, 20 Nov 2018 07:49:20 +0100 Subject: [PATCH 1/3] [11.0][mrp_multi_level] refactor mrp area --- mrp_multi_level/__manifest__.py | 21 +- .../demo/product_mrp_area_demo.xml | 27 ++ mrp_multi_level/models/__init__.py | 2 +- mrp_multi_level/models/mrp_inventory.py | 27 +- mrp_multi_level/models/mrp_move.py | 11 +- mrp_multi_level/models/mrp_product.py | 135 --------- mrp_multi_level/models/product_mrp_area.py | 143 +++++++++ mrp_multi_level/models/product_product.py | 59 ++-- mrp_multi_level/models/product_template.py | 244 ++------------- mrp_multi_level/security/ir.model.access.csv | 4 +- mrp_multi_level/tests/test_mrp_multi_level.py | 83 ++++-- .../{mrp_area_view.xml => mrp_area_views.xml} | 0 ...ntory_view.xml => mrp_inventory_views.xml} | 12 +- mrp_multi_level/views/mrp_menuitem.xml | 21 +- mrp_multi_level/views/mrp_product_view.xml | 161 ---------- .../views/product_mrp_area_views.xml | 92 ++++++ .../views/product_product_view.xml | 32 -- .../views/product_product_views.xml | 22 ++ .../views/product_template_view.xml | 31 -- .../views/product_template_views.xml | 22 ++ ...tion_view.xml => stock_location_views.xml} | 0 .../wizards/mrp_inventory_procure.py | 6 +- ...ew.xml => mrp_inventory_procure_views.xml} | 0 mrp_multi_level/wizards/mrp_multi_level.py | 282 +++++++++--------- ...vel_view.xml => mrp_multi_level_views.xml} | 0 25 files changed, 608 insertions(+), 829 deletions(-) create mode 100644 mrp_multi_level/demo/product_mrp_area_demo.xml delete mode 100644 mrp_multi_level/models/mrp_product.py create mode 100644 mrp_multi_level/models/product_mrp_area.py rename mrp_multi_level/views/{mrp_area_view.xml => mrp_area_views.xml} (100%) rename mrp_multi_level/views/{mrp_inventory_view.xml => mrp_inventory_views.xml} (92%) delete mode 100644 mrp_multi_level/views/mrp_product_view.xml create mode 100644 mrp_multi_level/views/product_mrp_area_views.xml delete mode 100644 mrp_multi_level/views/product_product_view.xml create mode 100644 mrp_multi_level/views/product_product_views.xml delete mode 100644 mrp_multi_level/views/product_template_view.xml create mode 100644 mrp_multi_level/views/product_template_views.xml rename mrp_multi_level/views/{stock_location_view.xml => stock_location_views.xml} (100%) rename mrp_multi_level/wizards/{mrp_inventory_procure_view.xml => mrp_inventory_procure_views.xml} (100%) rename mrp_multi_level/wizards/{mrp_multi_level_view.xml => mrp_multi_level_views.xml} (100%) diff --git a/mrp_multi_level/__manifest__.py b/mrp_multi_level/__manifest__.py index 0fd166eec..9c7b597e0 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.1.1.0', + 'version': '11.0.2.0.0', 'development_status': 'Beta', 'license': 'AGPL-3', 'author': 'Ucamco, ' @@ -23,14 +23,14 @@ 'data': [ 'security/mrp_multi_level_security.xml', 'security/ir.model.access.csv', - 'views/mrp_area_view.xml', - 'views/product_product_view.xml', - 'views/product_template_view.xml', - 'views/stock_location_view.xml', - 'views/mrp_product_view.xml', - 'wizards/mrp_inventory_procure_view.xml', - 'views/mrp_inventory_view.xml', - 'wizards/mrp_multi_level_view.xml', + 'views/mrp_area_views.xml', + 'views/product_product_views.xml', + 'views/product_template_views.xml', + 'views/product_mrp_area_views.xml', + 'views/stock_location_views.xml', + 'wizards/mrp_inventory_procure_views.xml', + 'views/mrp_inventory_views.xml', + 'wizards/mrp_multi_level_views.xml', 'views/mrp_menuitem.xml', 'data/mrp_multi_level_cron.xml', 'data/mrp_area_data.xml', @@ -38,7 +38,8 @@ 'demo': [ 'demo/product_category_demo.xml', 'demo/product_product_demo.xml', - 'demo/res_partner_demo.xml', + 'demo/product_product_demo.xml', + 'demo/product_mrp_area_demo.xml', 'demo/product_supplierinfo_demo.xml', 'demo/mrp_bom_demo.xml', 'demo/initial_on_hand_demo.xml', diff --git a/mrp_multi_level/demo/product_mrp_area_demo.xml b/mrp_multi_level/demo/product_mrp_area_demo.xml new file mode 100644 index 000000000..e3684dffd --- /dev/null +++ b/mrp_multi_level/demo/product_mrp_area_demo.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mrp_multi_level/models/__init__.py b/mrp_multi_level/models/__init__.py index 904be8277..0ca21df77 100644 --- a/mrp_multi_level/models/__init__.py +++ b/mrp_multi_level/models/__init__.py @@ -2,6 +2,6 @@ from . import mrp_area from . import stock_location from . import product_product from . import product_template -from . import mrp_product from . import mrp_move from . import mrp_inventory +from . import product_mrp_area diff --git a/mrp_multi_level/models/mrp_inventory.py b/mrp_multi_level/models/mrp_inventory.py index 13dd86d49..e8a36adfb 100644 --- a/mrp_multi_level/models/mrp_inventory.py +++ b/mrp_multi_level/models/mrp_inventory.py @@ -10,9 +10,9 @@ from datetime import timedelta, date class MrpInventory(models.Model): _name = 'mrp.inventory' - _order = 'mrp_product_id, date' + _order = 'product_mrp_area_id, date' _description = 'MRP inventory projections' - _rec_name = 'mrp_product_id' + _rec_name = 'product_mrp_area_id' # TODO: name to pass to procurements? # TODO: compute procurement_date to pass to the wizard? not needed for @@ -22,10 +22,10 @@ class MrpInventory(models.Model): mrp_area_id = fields.Many2one( comodel_name='mrp.area', string='MRP Area', - related='mrp_product_id.mrp_area_id', store=True, + related='product_mrp_area_id.mrp_area_id', store=True, ) - mrp_product_id = fields.Many2one( - comodel_name='mrp.product', string='Product', + product_mrp_area_id = fields.Many2one( + comodel_name='product.mrp.area', string='Product', index=True, ) uom_id = fields.Many2one( @@ -47,20 +47,21 @@ class MrpInventory(models.Model): @api.multi def _compute_uom_id(self): for rec in self: - rec.uom_id = rec.mrp_product_id.product_id.uom_id + rec.uom_id = rec.product_mrp_area_id.product_id.uom_id @api.multi - @api.depends('mrp_product_id', 'mrp_product_id.main_supplierinfo_id', - 'mrp_product_id.mrp_lead_time', - 'mrp_product_id.mrp_area_id.calendar_id') + @api.depends('product_mrp_area_id', + 'product_mrp_area_id.main_supplierinfo_id', + 'product_mrp_area_id.mrp_lead_time', + 'product_mrp_area_id.mrp_area_id.calendar_id') def _compute_order_release_date(self): today = date.today() for rec in self.filtered(lambda r: r.date): delay = 0 - if rec.mrp_product_id.supply_method == 'buy': - delay = rec.mrp_product_id.main_supplierinfo_id.delay - elif rec.mrp_product_id.supply_method == 'manufacture': - delay = rec.mrp_product_id.mrp_lead_time + if rec.product_mrp_area_id.supply_method == 'buy': + delay = rec.product_mrp_area_id.main_supplierinfo_id.delay + elif rec.product_mrp_area_id.supply_method == 'manufacture': + delay = rec.product_mrp_area_id.mrp_lead_time # TODO: 'move' supply method if delay and rec.mrp_area_id.calendar_id: dt_date = fields.Datetime.from_string(rec.date) diff --git a/mrp_multi_level/models/mrp_move.py b/mrp_multi_level/models/mrp_move.py index c04049096..9c14f970e 100644 --- a/mrp_multi_level/models/mrp_move.py +++ b/mrp_multi_level/models/mrp_move.py @@ -7,13 +7,16 @@ from odoo import models, fields class MrpMove(models.Model): _name = 'mrp.move' - _order = 'mrp_product_id, mrp_date, mrp_type desc, id' + _order = 'product_mrp_area_id, mrp_date, mrp_type desc, id' # TODO: too many indexes... mrp_area_id = fields.Many2one( comodel_name='mrp.area', + related='product_mrp_area_id.mrp_area_id', string='MRP Area', + store=True, + index=True, ) current_date = fields.Date(string='Current Date') current_qty = fields.Float(string='Current Qty') @@ -43,7 +46,7 @@ class MrpMove(models.Model): ) mrp_minimum_stock = fields.Float( string='Minimum Stock', - related='product_id.mrp_minimum_stock', + related='product_mrp_area_id.mrp_minimum_stock', ) mrp_order_number = fields.Char(string='Order Number') # TODO: replace by a char origin? @@ -54,8 +57,8 @@ class MrpMove(models.Model): ('fc', 'Forecast'), ('mrp', 'MRP')], string='Origin') mrp_processed = fields.Boolean(string='Processed') - mrp_product_id = fields.Many2one( - comodel_name='mrp.product', + product_mrp_area_id = fields.Many2one( + comodel_name='product.mrp.area', string='Product', index=True, ) mrp_qty = fields.Float(string='MRP Quantity') diff --git a/mrp_multi_level/models/mrp_product.py b/mrp_multi_level/models/mrp_product.py deleted file mode 100644 index 4a64c1b08..000000000 --- a/mrp_multi_level/models/mrp_product.py +++ /dev/null @@ -1,135 +0,0 @@ -# © 2016 Ucamco - Wim Audenaert -# © 2016-18 Eficent Business and IT Consulting Services S.L. -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -from math import ceil - -from odoo import api, fields, models - - -class MrpProduct(models.Model): - _name = 'mrp.product' - - mrp_area_id = fields.Many2one( - comodel_name='mrp.area', string='MRP Area', - ) - current_qty_available = fields.Float( - string='Current Qty Available', - related='product_id.qty_available', - ) - main_supplier_id = fields.Many2one( - comodel_name='res.partner', string='Main Supplier', - compute='_compute_main_supplier', store=True, - index=True, - ) - main_supplierinfo_id = fields.Many2one( - comodel_name='product.supplierinfo', string='Supplier Info', - compute='_compute_main_supplier', store=True, - ) - mrp_inspection_delay = fields.Integer( - string='Inspection Delay', - related='product_id.mrp_inspection_delay') - mrp_lead_time = fields.Float( - string='Lead Time', - related='product_id.produce_delay', - ) - mrp_llc = fields.Integer( - string='Low Level Code', index=True, readonly=True, - ) - # TODO: minimun stock and max/min order qty assigned by area? - mrp_maximum_order_qty = fields.Float( - string='Maximum Order Qty', - related='product_id.mrp_maximum_order_qty', - ) - mrp_minimum_order_qty = fields.Float( - string='Minimum Order Qty', - related='product_id.mrp_minimum_order_qty', - ) - mrp_minimum_stock = fields.Float( - string='Minimum Stock', - related='product_id.mrp_minimum_stock', - ) - mrp_move_ids = fields.One2many( - comodel_name='mrp.move', inverse_name='mrp_product_id', - string='MRP Moves', - ) - mrp_nbr_days = fields.Integer( - string='Nbr. Days', related='product_id.mrp_nbr_days') - mrp_qty_available = fields.Float( - string='MRP Qty Available') - mrp_qty_multiple = fields.Float( - string='Qty Multiple', - related='product_id.mrp_qty_multiple', - ) - mrp_transit_delay = fields.Integer(related='product_id.mrp_transit_delay') - mrp_verified = fields.Boolean( - string='MRP Verified', - related='product_id.mrp_verified', - ) - name = fields.Char(string='Description') - # TODO: rename to mrp_action_count? - nbr_mrp_actions = fields.Integer( - string='Nbr Actions', index=True, - ) - nbr_mrp_actions_4w = fields.Integer( - string='Nbr Actions 4 Weeks', index=True, - ) - product_id = fields.Many2one( - comodel_name='product.product', string='Product', - index=True, - ) - product_tmpl_id = fields.Many2one( - comodel_name='product.template', - string='Product Template', - related='product_id.product_tmpl_id', - ) - supply_method = fields.Selection( - selection=[('buy', 'Buy'), - ('none', 'Undefined'), - ('manufacture', 'Produce'), - ('move', 'Transfer')], - string='Supply Method', - compute='_compute_supply_method', store=True, - ) - - @api.multi - @api.depends('mrp_area_id') - def _compute_supply_method(self): - group_obj = self.env['procurement.group'] - for rec in self: - values = { - 'warehouse_id': rec.mrp_area_id.warehouse_id, - 'company_id': self.env.user.company_id.id, - # TODO: better way to get company - } - rule = group_obj._get_rule( - rec.product_id, rec.mrp_area_id.location_id, values) - rec.supply_method = rule.action if rule else 'none' - - @api.multi - @api.depends('supply_method') - def _compute_main_supplier(self): - """Simplified and similar to procurement.rule logic.""" - for rec in self.filtered(lambda r: r.supply_method == 'buy'): - suppliers = rec.product_id.seller_ids.filtered( - lambda r: (not r.product_id or r.product_id == rec.product_id)) - if not suppliers: - continue - rec.main_supplierinfo_id = suppliers[0] - rec.main_supplier_id = suppliers[0].name - - @api.multi - def _adjust_qty_to_order(self, qty_to_order): - self.ensure_one() - if (not self.mrp_maximum_order_qty and not - self.mrp_minimum_order_qty and self.mrp_qty_multiple == 1.0): - return qty_to_order - if qty_to_order < self.mrp_minimum_order_qty: - return self.mrp_minimum_order_qty - if self.mrp_qty_multiple: - multiplier = ceil(qty_to_order / self.mrp_qty_multiple) - qty_to_order = multiplier * self.mrp_qty_multiple - if self.mrp_maximum_order_qty and qty_to_order > \ - self.mrp_maximum_order_qty: - return self.mrp_maximum_order_qty - return qty_to_order diff --git a/mrp_multi_level/models/product_mrp_area.py b/mrp_multi_level/models/product_mrp_area.py new file mode 100644 index 000000000..780edd4ab --- /dev/null +++ b/mrp_multi_level/models/product_mrp_area.py @@ -0,0 +1,143 @@ +# Copyright 2016 Ucamco - Wim Audenaert +# Copyright 2016-18 Eficent Business and IT Consulting Services S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from math import ceil + +from odoo import api, fields, models + + +class ProductMRPArea(models.Model): + _name = 'product.mrp.area' + _description = 'Product MRP Area' + + active = fields.Boolean(default=True) + mrp_area_id = fields.Many2one('mrp.area', + required=True, + ) + product_id = fields.Many2one('product.product', + required=True, + string='Product', + ) + product_tmpl_id = fields.Many2one('product.template', + readonly=True, + related='product_id.product_tmpl_id', + store=True, + ) + + # TODO: applicable and exclude... redundant?? + mrp_applicable = fields.Boolean(string='MRP Applicable') + mrp_exclude = fields.Boolean(string='Exclude from MRP') + mrp_inspection_delay = fields.Integer(string='Inspection Delay') + mrp_maximum_order_qty = fields.Float( + string='Maximum Order Qty', default=0.0, + ) + mrp_minimum_order_qty = fields.Float( + string='Minimum Order Qty', default=0.0, + ) + mrp_minimum_stock = fields.Float(string='Minimum Stock') + mrp_nbr_days = fields.Integer( + string='Nbr. Days', default=0, + help="Number of days to group demand for this product during the " + "MRP run, in order to determine the quantity to order.", + ) + mrp_qty_multiple = fields.Float(string='Qty Multiple', default=1.00) + mrp_transit_delay = fields.Integer(string='Transit Delay', default=0) + mrp_verified = fields.Boolean( + string='Verified for MRP', + help="Identifies that this product has been verified " + "to be valid for the MRP.", + ) + mrp_lead_time = fields.Float( + string='Lead Time', + related='product_id.produce_delay', + ) + main_supplier_id = fields.Many2one( + comodel_name='res.partner', string='Main Supplier', + compute='_compute_main_supplier', store=True, + index=True, + ) + main_supplierinfo_id = fields.Many2one( + comodel_name='product.supplierinfo', string='Supplier Info', + compute='_compute_main_supplier', store=True, + ) + supply_method = fields.Selection( + selection=[('buy', 'Buy'), + ('none', 'Undefined'), + ('manufacture', 'Produce'), + ('move', 'Transfer')], + string='Supply Method', + compute='_compute_supply_method', + ) + + qty_available = fields.Float('Quantity Available', + compute='_compute_qty_available') + mrp_move_ids = fields.One2many(comodel_name='mrp.move', + inverse_name='product_mrp_area_id', + readonly=True, + ) + _sql_constraints = [ + ('product_mrp_area_uniq', 'unique(product_id, mrp_area_id)', + 'The product/MRP Area parameters combination must be unique.'), + ] + + @api.multi + def name_get(self): + return [(area.id, '[%s] %s' % ( + area.mrp_area_id.name, + area.product_id.display_name)) for area in self] + + @api.multi + def _compute_qty_available(self): + for rec in self: + qty_available = 0.0 + product_obj = self.env['product.product'] + # TODO: move mrp_qty_available computation, maybe unreserved?? + location_ids = self.env['stock.location'].search( + [('id', 'child_of', + rec.mrp_area_id.location_id.id)]) + for location in location_ids: + product_l = product_obj.with_context( + {'location': location.id}).browse(rec.product_id.id) + qty_available += product_l.qty_available + rec.qty_available = qty_available + + @api.multi + def _compute_supply_method(self): + group_obj = self.env['procurement.group'] + for rec in self: + values = { + 'warehouse_id': rec.mrp_area_id.warehouse_id, + 'company_id': self.env.user.company_id.id, + # TODO: better way to get company + } + rule = group_obj._get_rule( + rec.product_id, rec.mrp_area_id.location_id, values) + rec.supply_method = rule.action if rule else 'none' + + @api.multi + @api.depends('supply_method') + def _compute_main_supplier(self): + """Simplified and similar to procurement.rule logic.""" + for rec in self.filtered(lambda r: r.supply_method == 'buy'): + suppliers = rec.product_id.seller_ids.filtered( + lambda r: (not r.product_id or r.product_id == rec.product_id)) + if not suppliers: + continue + rec.main_supplierinfo_id = suppliers[0] + rec.main_supplier_id = suppliers[0].name + + @api.multi + def _adjust_qty_to_order(self, qty_to_order): + self.ensure_one() + if (not self.mrp_maximum_order_qty and not + self.mrp_minimum_order_qty and self.mrp_qty_multiple == 1.0): + return qty_to_order + if qty_to_order < self.mrp_minimum_order_qty: + return self.mrp_minimum_order_qty + if self.mrp_qty_multiple: + multiplier = ceil(qty_to_order / self.mrp_qty_multiple) + qty_to_order = multiplier * self.mrp_qty_multiple + if self.mrp_maximum_order_qty and qty_to_order > \ + self.mrp_maximum_order_qty: + return self.mrp_maximum_order_qty + return qty_to_order diff --git a/mrp_multi_level/models/product_product.py b/mrp_multi_level/models/product_product.py index f0339d26c..f0ed768c8 100644 --- a/mrp_multi_level/models/product_product.py +++ b/mrp_multi_level/models/product_product.py @@ -2,7 +2,7 @@ # Copyright 2016-18 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import fields, models +from odoo import api, fields, models class Product(models.Model): @@ -15,36 +15,37 @@ class Product(models.Model): string='Manufacturing Orders', domain=[('state', '=', 'draft')], ) - # TODO: applicable and exclude... redundant?? - mrp_applicable = fields.Boolean(string='MRP Applicable') - mrp_exclude = fields.Boolean(string='Exclude from MRP') - mrp_inspection_delay = fields.Integer(string='Inspection Delay') - mrp_maximum_order_qty = fields.Float( - string='Maximum Order Qty', default=0.0, - ) - mrp_minimum_order_qty = fields.Float( - string='Minimum Order Qty', default=0.0, - ) - mrp_minimum_stock = fields.Float(string='Minimum Stock') - mrp_nbr_days = fields.Integer( - string='Nbr. Days', default=0, - help="Number of days to group demand for this product during the " - "MRP run, in order to determine the quantity to order.", - ) - mrp_product_ids = fields.One2many( - comodel_name='mrp.product', - inverse_name='product_id', - string='MRP Product data', - ) - mrp_qty_multiple = fields.Float(string='Qty Multiple', default=1.00) - mrp_transit_delay = fields.Integer(string='Transit Delay', default=0) - mrp_verified = fields.Boolean( - string='Verified for MRP', - help="Identifies that this product has been verified " - "to be valid for the MRP.", - ) purchase_order_line_ids = fields.One2many( comodel_name='purchase.order.line', inverse_name='product_id', string='Purchase Orders', ) + mrp_area_ids = fields.One2many( + comodel_name='product.mrp.area', + inverse_name='product_id', + string='MRP Area parameters' + ) + mrp_area_count = fields.Integer( + string='MRP Area Parameter Count', + readonly=True, + compute='_compute_mrp_area_count') + + @api.multi + def _compute_mrp_area_count(self): + for rec in self: + rec.mrp_area_count = len(rec.mrp_area_ids) + + @api.multi + def action_view_mrp_area_parameters(self): + self.ensure_one() + action = self.env.ref('mrp_multi_level.product_mrp_area_action') + result = action.read()[0] + product_ids = self.ids + if len(product_ids) > 1: + result['domain'] = [('product_id', 'in', product_ids)] + else: + res = self.env.ref('mrp_multi_level.product_mrp_area_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = product_ids[0] + result['context'] = {'default_product_id': product_ids[0]} + return result diff --git a/mrp_multi_level/models/product_template.py b/mrp_multi_level/models/product_template.py index 6c9a3967a..67b8e76be 100644 --- a/mrp_multi_level/models/product_template.py +++ b/mrp_multi_level/models/product_template.py @@ -7,222 +7,34 @@ from odoo import api, fields, models class ProductTemplate(models.Model): _inherit = 'product.template' - mrp_applicable = fields.Boolean(string='MRP Applicable', - compute='_compute_mrp_applicable', - inverse='_set_mrp_applicable', store=True - ) - mrp_exclude = fields.Boolean(string='Exclude from MRP', - compute='_compute_mrp_exclude', - inverse='_set_mrp_exclude', store=True - ) - mrp_inspection_delay = fields.Integer( - string='Inspection Delay', - compute='_compute_mrp_inspection_delay', - inverse='_set_mrp_inspection_delay', - store=True - ) - mrp_maximum_order_qty = fields.Float( - string='Maximum Order Qty', - compute='_compute_mrp_maximum_order_qty', - inverse='_set_mrp_maximum_order_qty', store=True - ) - mrp_minimum_order_qty = fields.Float( - string='Minimum Order Qty', - compute='_compute_mrp_minimum_order_qty', - inverse='_set_mrp_minimum_order_qty', store=True - ) - mrp_minimum_stock = fields.Float( - string='Minimum Stock', - compute='_compute_mrp_minimum_stock', - inverse='_set_mrp_minimum_stock', store=True - ) - mrp_nbr_days = fields.Integer( - string='Nbr. Days', - compute='_compute_mrp_nbr_days', - inverse='_set_mrp_nbr_days', store=True, - help="Number of days to group demand for this product during the " - "MRP run, in order to determine the quantity to order.", - ) - mrp_qty_multiple = fields.Float( - string='Qty Multiple', default=1.00, - compute='_compute_mrp_qty_multiple', - inverse='_set_mrp_qty_multiple', store=True - ) - mrp_transit_delay = fields.Integer( - string='Transit Delay', default=0, - compute='_compute_mrp_transit_delay', - inverse='_set_mrp_transit_delay', store=True - ) - mrp_verified = fields.Boolean( - string='Verified for MRP', - compute='_compute_mrp_verified', - inverse='_set_mrp_verified', store=True, - help="Identifies that this product has been verified " - "to be valid for the MRP.", + mrp_area_ids = fields.One2many( + comodel_name='product.mrp.area', + inverse_name='product_tmpl_id', + string='MRP Area parameters', ) + mrp_area_count = fields.Integer( + string='MRP Area Parameter Count', + readonly=True, + compute='_compute_mrp_area_count') - @api.depends('product_variant_ids', 'product_variant_ids.mrp_applicable') - def _compute_mrp_applicable(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_applicable = \ - template.product_variant_ids.mrp_applicable - for template in (self - unique_variants): - template.mrp_applicable = False + @api.multi + def _compute_mrp_area_count(self): + for rec in self: + rec.mrp_area_count = len(rec.mrp_area_ids) - @api.one - def _set_mrp_applicable(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_applicable = self.mrp_applicable - - @api.depends('product_variant_ids', 'product_variant_ids.mrp_exclude') - def _compute_mrp_exclude(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_exclude = \ - template.product_variant_ids.mrp_exclude - for template in (self - unique_variants): - template.mrp_exclude = False - - @api.one - def _set_mrp_exclude(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_exclude = self.mrp_exclude - - @api.depends('product_variant_ids', - 'product_variant_ids.mrp_inspection_delay') - def _compute_mrp_inspection_delay(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_inspection_delay = \ - template.product_variant_ids.mrp_inspection_delay - for template in (self - unique_variants): - template.mrp_inspection_delay = 0 - - @api.one - def _set_mrp_inspection_delay(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_inspection_delay = \ - self.mrp_inspection_delay - - @api.depends('product_variant_ids', - 'product_variant_ids.mrp_maximum_order_qty') - def _compute_mrp_maximum_order_qty(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_maximum_order_qty = \ - template.product_variant_ids.mrp_maximum_order_qty - for template in (self - unique_variants): - template.mrp_maximum_order_qty = 0.0 - - @api.one - def _set_mrp_maximum_order_qty(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_maximum_order_qty = \ - self.mrp_maximum_order_qty - - @api.depends('product_variant_ids', - 'product_variant_ids.mrp_minimum_order_qty') - def _compute_mrp_minimum_order_qty(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_minimum_order_qty = \ - template.product_variant_ids.mrp_minimum_order_qty - for template in (self - unique_variants): - template.mrp_minimum_order_qty = 0.0 - - @api.one - def _set_mrp_minimum_order_qty(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_minimum_order_qty = \ - self.mrp_minimum_order_qty - - @api.depends('product_variant_ids', - 'product_variant_ids.mrp_minimum_stock') - def _compute_mrp_minimum_stock(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_minimum_stock = \ - template.product_variant_ids.mrp_minimum_stock - for template in (self - unique_variants): - template.mrp_minimum_stock = 0.0 - - @api.one - def _set_mrp_minimum_stock(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_minimum_stock = \ - self.mrp_minimum_stock - - @api.depends('product_variant_ids', - 'product_variant_ids.mrp_nbr_days') - def _compute_mrp_nbr_days(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_nbr_days = \ - template.product_variant_ids.mrp_nbr_days - for template in (self - unique_variants): - template.mrp_nbr_days = 0.0 - - @api.one - def _set_mrp_nbr_days(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_nbr_days = \ - self.mrp_nbr_days - - @api.depends('product_variant_ids', - 'product_variant_ids.mrp_qty_multiple') - def _compute_mrp_qty_multiple(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_qty_multiple = \ - template.product_variant_ids.mrp_qty_multiple - for template in (self - unique_variants): - template.mrp_qty_multiple = 1 - - @api.one - def _set_mrp_qty_multiple(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_qty_multiple = \ - self.mrp_qty_multiple - - @api.depends('product_variant_ids', - 'product_variant_ids.mrp_transit_delay') - def _compute_mrp_transit_delay(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_transit_delay = \ - template.product_variant_ids.mrp_transit_delay - for template in (self - unique_variants): - template.mrp_transit_delay = 0.0 - - @api.one - def _set_mrp_transit_delay(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_transit_delay = \ - self.mrp_transit_delay - - @api.depends('product_variant_ids', - 'product_variant_ids.mrp_verified') - def _compute_mrp_verified(self): - unique_variants = self.filtered( - lambda template: len(template.product_variant_ids) == 1) - for template in unique_variants: - template.mrp_verified = \ - template.product_variant_ids.mrp_verified - for template in (self - unique_variants): - template.mrp_verified = 0.0 - - @api.one - def _set_mrp_verified(self): - if len(self.product_variant_ids) == 1: - self.product_variant_ids.mrp_verified = \ - self.mrp_verified + @api.multi + def action_view_mrp_area_parameters(self): + self.ensure_one() + action = self.env.ref('mrp_multi_level.product_mrp_area_action') + result = action.read()[0] + mrp_area_ids = self.with_context( + active_test=False).mrp_area_ids.ids + if len(mrp_area_ids) > 1: + result['domain'] = [('id', 'in', mrp_area_ids)] + else: + res = self.env.ref('mrp_multi_level.product_mrp_area_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = mrp_area_ids[0] + result['context'] = { + 'default_product_id': self.product_variant_ids[0].id} + return result diff --git a/mrp_multi_level/security/ir.model.access.csv b/mrp_multi_level/security/ir.model.access.csv index 2e2cafd52..953d83705 100644 --- a/mrp_multi_level/security/ir.model.access.csv +++ b/mrp_multi_level/security/ir.model.access.csv @@ -3,7 +3,7 @@ access_mrp_inventory_user,mrp.inventory user,model_mrp_inventory,mrp.group_mrp_u access_mrp_inventory_manager,mrp.inventory manager,model_mrp_inventory,mrp.group_mrp_manager,1,1,1,1 access_mrp_move_user,mrp.move user,model_mrp_move,mrp.group_mrp_user,1,0,0,0 access_mrp_move_manager,mrp.move manager,model_mrp_move,mrp.group_mrp_manager,1,1,1,1 -access_mrp_product_user,mrp.product user,model_mrp_product,base.group_user,1,0,0,0 -access_mrp_product_manager,mrp.product manager,model_mrp_product,mrp.group_mrp_manager,1,1,1,1 access_mrp_area_user,mrp.area user,model_mrp_area,mrp.group_mrp_user,1,0,0,0 access_mrp_area_manager,mrp.area manager,model_mrp_area,mrp.group_mrp_manager,1,1,1,1 +access_product_mrp_area_user,product.mrp.area user,model_product_mrp_area,mrp.group_mrp_user,1,1,1,0 +access_product_mrp_area_manager,product.mrp.area manager,model_product_mrp_area,mrp.group_mrp_manager,1,1,1,1 diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py index 098b713b9..1d638fd65 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -17,13 +17,13 @@ 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.product_mrp_area_obj = cls.env['product.mrp.area'] cls.partner_obj = cls.env['res.partner'] 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'] cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure'] - cls.mrp_inventory_obj = cls.env['mrp.inventory'] - cls.mrp_product_obj = cls.env['mrp.product'] + cls.mrp_inventory_obj = cls.env['mrp.inventory'] cls.mrp_move_obj = cls.env['mrp.move'] cls.fp_1 = cls.env.ref('mrp_multi_level.product_product_fp_1') @@ -32,6 +32,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.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') cls.stock_location = cls.wh.lot_stock_id @@ -54,22 +55,35 @@ class TestMrpMultiLevel(SavepointCase): 'route_ids': [(6, 0, [route_buy])], 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 20.0})], }) + cls.product_mrp_area_obj.create({ + 'product_id': cls.prod_test.id, + 'mrp_area_id': cls.mrp_area.id, + }) cls.prod_min = cls.product_obj.create({ 'name': 'Product with minimum order qty', 'type': 'product', 'list_price': 50.0, 'route_ids': [(6, 0, [route_buy])], 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})], + }) + cls.product_mrp_area_obj.create({ + 'product_id': cls.prod_min.id, + 'mrp_area_id': cls.mrp_area.id, 'mrp_minimum_order_qty': 50.0, 'mrp_maximum_order_qty': 0.0, 'mrp_qty_multiple': 1.0, }) + cls.prod_max = cls.product_obj.create({ 'name': 'Product with maximum order qty', 'type': 'product', 'list_price': 50.0, 'route_ids': [(6, 0, [route_buy])], 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})], + }) + cls.product_mrp_area_obj.create({ + 'product_id': cls.prod_max.id, + 'mrp_area_id': cls.mrp_area.id, 'mrp_minimum_order_qty': 50.0, 'mrp_maximum_order_qty': 100.0, 'mrp_qty_multiple': 1.0, @@ -80,11 +94,14 @@ class TestMrpMultiLevel(SavepointCase): 'list_price': 50.0, 'route_ids': [(6, 0, [route_buy])], 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})], + }) + cls.product_mrp_area_obj.create({ + 'product_id': cls.prod_multiple.id, + 'mrp_area_id': cls.mrp_area.id, 'mrp_minimum_order_qty': 50.0, 'mrp_maximum_order_qty': 500.0, 'mrp_qty_multiple': 25.0, }) - # Create test picking for FP-1 and FP-2: res = cls.calendar.plan_days(7+1, datetime.today()) date_move = res.date() @@ -244,16 +261,16 @@ class TestMrpMultiLevel(SavepointCase): self.assertEqual(self.pp_1.llc, 2) self.assertEqual(self.pp_2.llc, 2) - def test_02_mrp_product(self): + def test_02_product_mrp_area(self): """Tests that mrp products are generated correctly.""" - mrp_product = self.mrp_product_obj.search([ + product_mrp_area = self.product_mrp_area_obj.search([ ('product_id', '=', self.pp_1.id)]) - self.assertEqual(mrp_product.supply_method, 'buy') - self.assertEqual(mrp_product.main_supplier_id, self.vendor) - self.assertEqual(mrp_product.mrp_qty_available, 10.0) - mrp_product = self.mrp_product_obj.search([ + self.assertEqual(product_mrp_area.supply_method, 'buy') + self.assertEqual(product_mrp_area.main_supplier_id, self.vendor) + self.assertEqual(product_mrp_area.qty_available, 10.0) + product_mrp_area = self.product_mrp_area_obj.search([ ('product_id', '=', self.sf_1.id)]) - self.assertEqual(mrp_product.supply_method, 'manufacture') + self.assertEqual(product_mrp_area.supply_method, 'manufacture') def test_03_mrp_moves(self): """Tests for mrp moves generated.""" @@ -265,11 +282,13 @@ class TestMrpMultiLevel(SavepointCase): self.assertNotIn('s', moves.mapped('mrp_type')) for move in moves: self.assertTrue(move.mrp_move_up_ids) - if move.mrp_move_up_ids.mrp_product_id.product_id == self.fp_1: + if move.mrp_move_up_ids.product_mrp_area_id.product_id == \ + self.fp_1: # Demand coming from FP-1 self.assertEqual(move.mrp_move_up_ids.mrp_action, 'mo') self.assertEqual(move.mrp_qty, -200.0) - elif move.mrp_move_up_ids.mrp_product_id.product_id == self.sf_1: + elif move.mrp_move_up_ids.product_mrp_area_id.product_id == \ + self.sf_1: # Demand coming from FP-2 -> SF-1 self.assertEqual(move.mrp_move_up_ids.mrp_action, 'mo') if move.mrp_date == self.date_5: @@ -299,21 +318,21 @@ class TestMrpMultiLevel(SavepointCase): """Tests MRP inventories created.""" # FP-1 fp_1_inventory_lines = self.mrp_inventory_obj.search( - [('mrp_product_id.product_id', '=', self.fp_1.id)]) + [('product_mrp_area_id.product_id', '=', self.fp_1.id)]) self.assertEqual(len(fp_1_inventory_lines), 1) self.assertEqual(fp_1_inventory_lines.date, self.date_7) self.assertEqual(fp_1_inventory_lines.demand_qty, 100.0) self.assertEqual(fp_1_inventory_lines.to_procure, 100.0) # FP-2 fp_2_line_1 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.fp_2.id), + ('product_mrp_area_id.product_id', '=', self.fp_2.id), ('date', '=', self.date_7)]) self.assertEqual(len(fp_2_line_1), 1) self.assertEqual(fp_2_line_1.demand_qty, 15.0) self.assertEqual(fp_2_line_1.to_procure, 15.0) # TODO: ask odoo to fix it... should be date10 fp_2_line_2 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.fp_2.id), + ('product_mrp_area_id.product_id', '=', self.fp_2.id), ('date', '=', self.date_9)]) self.assertEqual(len(fp_2_line_2), 1) self.assertEqual(fp_2_line_2.demand_qty, 0.0) @@ -322,26 +341,26 @@ class TestMrpMultiLevel(SavepointCase): # SF-1 sf_1_line_1 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.sf_1.id), + ('product_mrp_area_id.product_id', '=', self.sf_1.id), ('date', '=', self.date_6)]) self.assertEqual(len(sf_1_line_1), 1) self.assertEqual(sf_1_line_1.demand_qty, 30.0) self.assertEqual(sf_1_line_1.to_procure, 30.0) sf_1_line_2 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.sf_1.id), + ('product_mrp_area_id.product_id', '=', self.sf_1.id), ('date', '=', self.date_9)]) self.assertEqual(len(sf_1_line_2), 1) self.assertEqual(sf_1_line_2.demand_qty, 24.0) self.assertEqual(sf_1_line_2.to_procure, 24.0) # SF-2 sf_2_line_1 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.sf_2.id), + ('product_mrp_area_id.product_id', '=', self.sf_2.id), ('date', '=', self.date_6)]) self.assertEqual(len(sf_2_line_1), 1) self.assertEqual(sf_2_line_1.demand_qty, 45.0) self.assertEqual(sf_2_line_1.to_procure, 30.0) sf_2_line_2 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.sf_2.id), + ('product_mrp_area_id.product_id', '=', self.sf_2.id), ('date', '=', self.date_9)]) self.assertEqual(len(sf_2_line_2), 1) self.assertEqual(sf_2_line_2.demand_qty, 36.0) @@ -349,39 +368,39 @@ class TestMrpMultiLevel(SavepointCase): # PP-1 pp_1_line_1 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.pp_1.id), + ('product_mrp_area_id.product_id', '=', self.pp_1.id), ('date', '=', self.date_5)]) self.assertEqual(len(pp_1_line_1), 1) self.assertEqual(pp_1_line_1.demand_qty, 290.0) self.assertEqual(pp_1_line_1.to_procure, 280.0) pp_1_line_2 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.pp_1.id), + ('product_mrp_area_id.product_id', '=', self.pp_1.id), ('date', '=', self.date_8)]) self.assertEqual(len(pp_1_line_2), 1) self.assertEqual(pp_1_line_2.demand_qty, 72.0) self.assertEqual(pp_1_line_2.to_procure, 72.0) # PP-2 pp_2_line_1 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.pp_2.id), + ('product_mrp_area_id.product_id', '=', self.pp_2.id), ('date', '=', self.date_3)]) self.assertEqual(len(pp_2_line_1), 1) self.assertEqual(pp_2_line_1.demand_qty, 90.0) # 90.0 demand - 20.0 on hand - 5.0 on PO = 65.0 self.assertEqual(pp_2_line_1.to_procure, 65.0) pp_2_line_2 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.pp_2.id), + ('product_mrp_area_id.product_id', '=', self.pp_2.id), ('date', '=', self.date_5)]) self.assertEqual(len(pp_2_line_2), 1) self.assertEqual(pp_2_line_2.demand_qty, 360.0) self.assertEqual(pp_2_line_2.to_procure, 360.0) pp_2_line_3 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.pp_2.id), + ('product_mrp_area_id.product_id', '=', self.pp_2.id), ('date', '=', self.date_6)]) self.assertEqual(len(pp_2_line_3), 1) self.assertEqual(pp_2_line_3.demand_qty, 108.0) self.assertEqual(pp_2_line_3.to_procure, 108.0) pp_2_line_4 = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.pp_2.id), + ('product_mrp_area_id.product_id', '=', self.pp_2.id), ('date', '=', self.date_8)]) self.assertEqual(len(pp_2_line_4), 1) self.assertEqual(pp_2_line_4.demand_qty, 48.0) @@ -398,11 +417,11 @@ class TestMrpMultiLevel(SavepointCase): 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: - mrp_product = self.mrp_product_obj.search([ + product_mrp_area = self.product_mrp_area_obj.search([ ('product_id', '=', self.pp_1.id) ]) - self.assertEqual(mrp_product.nbr_mrp_actions, 3) - self.assertEqual(mrp_product.nbr_mrp_actions_4w, 3) + # 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.""" @@ -428,7 +447,7 @@ class TestMrpMultiLevel(SavepointCase): ('product_id', '=', self.fp_1.id)]) self.assertFalse(mos) mrp_inv = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.fp_1.id)]) + ('product_mrp_area_id.product_id', '=', self.fp_1.id)]) self.mrp_inventory_procure_wiz.with_context({ 'active_model': 'mrp.inventory', 'active_ids': mrp_inv.ids, @@ -446,11 +465,11 @@ class TestMrpMultiLevel(SavepointCase): maximum order quantities and quantity multiple are set.""" # minimum order quantity: mrp_inv_min = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.prod_min.id)]) + ('product_mrp_area_id.product_id', '=', self.prod_min.id)]) self.assertEqual(mrp_inv_min.to_procure, 50.0) # maximum order quantity: mrp_inv_max = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.prod_max.id)]) + ('product_mrp_area_id.product_id', '=', self.prod_max.id)]) self.assertEqual(mrp_inv_max.to_procure, 150) moves = self.mrp_move_obj.search([ ('product_id', '=', self.prod_max.id), @@ -461,7 +480,7 @@ class TestMrpMultiLevel(SavepointCase): self.assertIn(50.0, moves.mapped('mrp_qty')) # quantity multiple: mrp_inv_multiple = self.mrp_inventory_obj.search([ - ('mrp_product_id.product_id', '=', self.prod_multiple.id)]) + ('product_mrp_area_id.product_id', '=', self.prod_multiple.id)]) self.assertEqual(mrp_inv_multiple.to_procure, 125) # TODO: test procure wizard: pos, multiple... diff --git a/mrp_multi_level/views/mrp_area_view.xml b/mrp_multi_level/views/mrp_area_views.xml similarity index 100% rename from mrp_multi_level/views/mrp_area_view.xml rename to mrp_multi_level/views/mrp_area_views.xml diff --git a/mrp_multi_level/views/mrp_inventory_view.xml b/mrp_multi_level/views/mrp_inventory_views.xml similarity index 92% rename from mrp_multi_level/views/mrp_inventory_view.xml rename to mrp_multi_level/views/mrp_inventory_views.xml index 453b6da0f..484df5d7b 100644 --- a/mrp_multi_level/views/mrp_inventory_view.xml +++ b/mrp_multi_level/views/mrp_inventory_views.xml @@ -11,7 +11,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -59,7 +59,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -84,7 +84,7 @@ - + @@ -93,7 +93,7 @@ + context="{'group_by':'product_mrp_area_id'}"/> - + - - - - diff --git a/mrp_multi_level/views/mrp_product_view.xml b/mrp_multi_level/views/mrp_product_view.xml deleted file mode 100644 index 9854c446b..000000000 --- a/mrp_multi_level/views/mrp_product_view.xml +++ /dev/null @@ -1,161 +0,0 @@ - - - - - mrp.product.tree - mrp.product - tree - - - - - - - - - - - - - - - - - mrp.product.form - mrp.product - form - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - mrp.filter.form - mrp.product - search - - - - - - - - - - - - - - - - - - - - - - - - MRP Products - mrp.product - ir.actions.act_window - form - tree,form - - - - -
diff --git a/mrp_multi_level/views/product_mrp_area_views.xml b/mrp_multi_level/views/product_mrp_area_views.xml new file mode 100644 index 000000000..8ab78a361 --- /dev/null +++ b/mrp_multi_level/views/product_mrp_area_views.xml @@ -0,0 +1,92 @@ + + + + + product.mrp.area.tree + product.mrp.area + tree + + + + + + + + + + + + + + + + + + + + + + product.mrp.area.form + product.mrp.area + form + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + product.mrp.area.search + product.mrp.area + search + + + + + + + + + + + + + Product MRP Area Parameters + product.mrp.area + ir.actions.act_window + form + tree,form + + + + +
diff --git a/mrp_multi_level/views/product_product_view.xml b/mrp_multi_level/views/product_product_view.xml deleted file mode 100644 index 8bb1aed12..000000000 --- a/mrp_multi_level/views/product_product_view.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - view.mrp.product.product.form - product.product - - form - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mrp_multi_level/views/product_product_views.xml b/mrp_multi_level/views/product_product_views.xml new file mode 100644 index 000000000..65014f376 --- /dev/null +++ b/mrp_multi_level/views/product_product_views.xml @@ -0,0 +1,22 @@ + + + + + view.product.mrp.area.product.form + product.product + + form + +
+ +
+
+
+
diff --git a/mrp_multi_level/views/product_template_view.xml b/mrp_multi_level/views/product_template_view.xml deleted file mode 100644 index 97a45f396..000000000 --- a/mrp_multi_level/views/product_template_view.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - product.template.product.form.mrp - product.template - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mrp_multi_level/views/product_template_views.xml b/mrp_multi_level/views/product_template_views.xml new file mode 100644 index 000000000..843413d55 --- /dev/null +++ b/mrp_multi_level/views/product_template_views.xml @@ -0,0 +1,22 @@ + + + + + product.template.product.form.mrp + product.template + + +
+ +
+
+
+ +
diff --git a/mrp_multi_level/views/stock_location_view.xml b/mrp_multi_level/views/stock_location_views.xml similarity index 100% rename from mrp_multi_level/views/stock_location_view.xml rename to mrp_multi_level/views/stock_location_views.xml diff --git a/mrp_multi_level/wizards/mrp_inventory_procure.py b/mrp_multi_level/wizards/mrp_inventory_procure.py index 14b3bf6be..57fe8df8c 100644 --- a/mrp_multi_level/wizards/mrp_inventory_procure.py +++ b/mrp_multi_level/wizards/mrp_inventory_procure.py @@ -23,7 +23,7 @@ class MrpInventoryProcure(models.TransientModel): 'uom_id': mrp_inventory.uom_id.id, 'date_planned': mrp_inventory.date, 'mrp_inventory_id': mrp_inventory.id, - 'product_id': mrp_inventory.mrp_product_id.product_id.id, + 'product_id': mrp_inventory.product_mrp_area_id.product_id.id, 'warehouse_id': mrp_inventory.mrp_area_id.warehouse_id.id, 'location_id': mrp_inventory.mrp_area_id.location_id.id, } @@ -57,12 +57,12 @@ class MrpInventoryProcure(models.TransientModel): items = item_obj = self.env['mrp.inventory.procure.item'] for line in mrp_inventory_obj.browse(mrp_inventory_ids): - max_order = line.mrp_product_id.mrp_maximum_order_qty + max_order = line.product_mrp_area_id.mrp_maximum_order_qty qty_to_order = line.to_procure if max_order and max_order < qty_to_order: # split the procurement in batches: while qty_to_order > 0.0: - qty = line.mrp_product_id._adjust_qty_to_order( + qty = line.product_mrp_area_id._adjust_qty_to_order( qty_to_order) items += item_obj.create(self._prepare_item(line, qty)) qty_to_order -= qty diff --git a/mrp_multi_level/wizards/mrp_inventory_procure_view.xml b/mrp_multi_level/wizards/mrp_inventory_procure_views.xml similarity index 100% rename from mrp_multi_level/wizards/mrp_inventory_procure_view.xml rename to mrp_multi_level/wizards/mrp_inventory_procure_views.xml diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py index 4244977de..16269844f 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -17,38 +17,37 @@ class MultiLevelMrp(models.TransientModel): # TODO: dates are not being correctly computed for supply... @api.model - def _prepare_mrp_product_data(self, product, mrp_area): + def _prepare_product_mrp_area_data(self, product_mrp_area): qty_available = 0.0 product_obj = self.env['product.product'] # TODO: move mrp_qty_available computation, maybe unreserved?? location_ids = self.env['stock.location'].search( - [('id', 'child_of', mrp_area.location_id.id)]) + [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)]) for location in location_ids: product_l = product_obj.with_context( - {'location': location.id}).browse(product.id) + {'location': location.id}).browse( + product_mrp_area.product_id.id) qty_available += product_l.qty_available return { - 'mrp_area_id': mrp_area.id, - 'product_id': product.id, - 'mrp_qty_available': product.qty_available, - 'mrp_llc': product.llc, - 'name': '[%s] %s' % (mrp_area.name, product.display_name), + 'product_mrp_area_id': product_mrp_area.id, + 'mrp_qty_available': qty_available, + 'mrp_llc': product_mrp_area.product_id.llc, } @api.model def _prepare_mrp_move_data_from_forecast( - self, estimate, mrp_product, date): + self, estimate, product_mrp_area, date): mrp_type = 'd' origin = 'fc' daily_qty = float_round( estimate.daily_qty, - precision_rounding=mrp_product.product_id.uom_id.rounding, + precision_rounding=product_mrp_area.product_id.uom_id.rounding, rounding_method='HALF-UP') return { - 'mrp_area_id': mrp_product.mrp_area_id.id, - 'product_id': mrp_product.product_id.id, - 'mrp_product_id': mrp_product.id, + 'mrp_area_id': product_mrp_area.mrp_area_id.id, + 'product_id': product_mrp_area.product_id.id, + 'product_mrp_area_id': product_mrp_area.id, 'production_id': None, 'purchase_order_id': None, 'purchase_line_id': None, @@ -70,7 +69,7 @@ class MultiLevelMrp(models.TransientModel): @api.model def _prepare_mrp_move_data_from_stock_move( - self, mrp_product, move, direction='in'): + self, product_mrp_area, move, direction='in'): if not((move.location_id.usage == 'internal' and move.location_dest_id.usage != 'internal') or (move.location_id.usage != 'internal' and @@ -117,9 +116,8 @@ class MultiLevelMrp(models.TransientModel): mrp_date = datetime.date(datetime.strptime( move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)) return { - 'mrp_area_id': mrp_product.mrp_area_id.id, 'product_id': move.product_id.id, - 'mrp_product_id': mrp_product.id, + 'product_mrp_area_id': product_mrp_area.id, 'production_id': mo, 'purchase_order_id': po, 'purchase_line_id': po_line, @@ -141,12 +139,11 @@ class MultiLevelMrp(models.TransientModel): @api.model def _prepare_mrp_move_data_supply( - self, mrp_product, qty, mrp_date_supply, mrp_action_date, + self, product_mrp_area, qty, mrp_date_supply, mrp_action_date, mrp_action, name): return { - 'mrp_area_id': mrp_product.mrp_area_id.id, - 'product_id': mrp_product.product_id.id, - 'mrp_product_id': mrp_product.id, + 'product_id': product_mrp_area.product_id.id, + 'product_mrp_area_id': product_mrp_area.id, 'production_id': None, 'purchase_order_id': None, 'purchase_line_id': None, @@ -168,16 +165,16 @@ class MultiLevelMrp(models.TransientModel): @api.model def _prepare_mrp_move_data_bom_explosion( self, product, bomline, qty, mrp_date_demand_2, bom, name): - mrp_product = self._get_mrp_product_from_product_and_area( + product_mrp_area = self._get_product_mrp_area_from_product_and_area( bomline.product_id, product.mrp_area_id) - if not mrp_product: + if not product_mrp_area: raise exceptions.Warning( _("No MRP product found")) return { 'mrp_area_id': product.mrp_area_id.id, 'product_id': bomline.product_id.id, - 'mrp_product_id': mrp_product.id, + 'product_mrp_area_id': product_mrp_area.id, 'production_id': None, 'purchase_order_id': None, 'purchase_line_id': None, @@ -200,15 +197,15 @@ class MultiLevelMrp(models.TransientModel): } @api.model - def create_move(self, mrp_product_id, mrp_date, mrp_qty, name): + def create_move(self, product_mrp_area_id, mrp_date, mrp_qty, name): self = self.with_context(auditlog_disabled=True) values = {} if not isinstance(mrp_date, date): mrp_date = fields.Date.from_string(mrp_date) - if mrp_product_id.supply_method == 'buy': - # if mrp_product_id.purchase_requisition: + if product_mrp_area_id.supply_method == 'buy': + # if product_mrp_area_id.purchase_requisition: # mrp_action = 'pr' # else: mrp_action = 'po' @@ -221,24 +218,24 @@ class MultiLevelMrp(models.TransientModel): else: mrp_date_supply = mrp_date - calendar = mrp_product_id.mrp_area_id.calendar_id - if calendar and mrp_product_id.mrp_lead_time: + calendar = product_mrp_area_id.mrp_area_id.calendar_id + if calendar and product_mrp_area_id.mrp_lead_time: date_str = fields.Date.to_string(mrp_date) dt = fields.Datetime.from_string(date_str) res = calendar.plan_days( - -1 * mrp_product_id.mrp_lead_time - 1, dt) + -1 * product_mrp_area_id.mrp_lead_time - 1, dt) mrp_action_date = res.date() else: mrp_action_date = mrp_date - timedelta( - days=mrp_product_id.mrp_lead_time) + days=product_mrp_area_id.mrp_lead_time) qty_ordered = 0.00 qty_to_order = mrp_qty while qty_ordered < mrp_qty: - qty = mrp_product_id._adjust_qty_to_order(qty_to_order) + qty = product_mrp_area_id._adjust_qty_to_order(qty_to_order) qty_to_order -= qty move_data = self._prepare_mrp_move_data_supply( - mrp_product_id, qty, mrp_date_supply, mrp_action_date, + product_mrp_area_id, qty, mrp_date_supply, mrp_action_date, mrp_action, name) mrpmove_id = self.env['mrp.move'].create(move_data) qty_ordered = qty_ordered + qty @@ -247,10 +244,10 @@ class MultiLevelMrp(models.TransientModel): mrp_date_demand = mrp_action_date if mrp_date_demand < date.today(): mrp_date_demand = date.today() - if not mrp_product_id.product_id.bom_ids: + if not product_mrp_area_id.product_id.bom_ids: continue bomcount = 0 - for bom in mrp_product_id.product_id.bom_ids: + for bom in product_mrp_area_id.product_id.bom_ids: if not bom.active or not bom.bom_line_ids: continue bomcount += 1 @@ -261,17 +258,17 @@ class MultiLevelMrp(models.TransientModel): bomline.product_id.type != 'product': continue if self._exclude_from_mrp( - mrp_product_id.mrp_area_id, - bomline.product_id): + bomline.product_id, + product_mrp_area_id.mrp_area_id): # Stop explosion. continue # TODO: review: mrp_transit_delay, mrp_inspection_delay mrp_date_demand_2 = mrp_date_demand - timedelta( - days=(mrp_product_id.mrp_transit_delay + - mrp_product_id.mrp_inspection_delay)) + days=(product_mrp_area_id.mrp_transit_delay + + product_mrp_area_id.mrp_inspection_delay)) move_data = \ self._prepare_mrp_move_data_bom_explosion( - mrp_product_id, bomline, qty, + product_mrp_area_id, bomline, qty, mrp_date_demand_2, bom, name) mrpmove_id2 = self.env['mrp.move'].create(move_data) @@ -288,7 +285,6 @@ class MultiLevelMrp(models.TransientModel): # installed logger.info('START MRP CLEANUP') self.env['mrp.move'].search([]).unlink() - self.env['mrp.product'].search([]).unlink() self.env['mrp.inventory'].search([]).unlink() logger.info('END MRP CLEANUP') return True @@ -335,31 +331,25 @@ class MultiLevelMrp(models.TransientModel): @api.model def _calculate_mrp_applicable(self): logger.info('CALCULATE MRP APPLICABLE') - self.env['product.product'].search([]).write({'mrp_applicable': False}) - self.env['product.product'].search([ - ('type', '=', 'product'), + self.env['product.mrp.area'].search([]).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.product'].search([ + counter = self.env['product.mrp.area'].search([ ('mrp_applicable', '=', True)], count=True) log_msg = 'END CALCULATE MRP APPLICABLE: %s' % counter logger.info(log_msg) return True @api.model - def _init_mrp_product(self, product, mrp_area): - - mrp_product_data = self._prepare_mrp_product_data( - product, mrp_area) - return self.env['mrp.product'].create(mrp_product_data) - - @api.model - def _init_mrp_move_from_forecast(self, mrp_product): + def _init_mrp_move_from_forecast(self, product_mrp_area): locations = self.env['stock.location'].search( - [('id', 'child_of', mrp_product.mrp_area_id.location_id.id)]) + [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)]) today = fields.Date.today() estimates = self.env['stock.demand.estimate'].search([ - ('product_id', '=', mrp_product.product_id.id), + ('product_id', '=', product_mrp_area.product_id.id), ('location_id', 'in', locations.ids), ('date_range_id.date_end', '>=', today) ]) @@ -373,19 +363,19 @@ class MultiLevelMrp(models.TransientModel): while mrp_date <= date_end: mrp_move_data = \ self._prepare_mrp_move_data_from_forecast( - rec, mrp_product, mrp_date) + rec, product_mrp_area, 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 + # TODO: move this methods to product_mrp_area?? to be able to # show moves with an action @api.model - def _in_stock_moves_domain(self, mrp_product): + def _in_stock_moves_domain(self, product_mrp_area): locations = self.env['stock.location'].search( - [('id', 'child_of', mrp_product.mrp_area_id.location_id.id)]) + [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)]) return [ - ('product_id', '=', mrp_product.product_id.id), + ('product_id', '=', product_mrp_area.product_id.id), ('state', 'not in', ['done', 'cancel']), ('product_qty', '>', 0.00), ('location_id', 'not in', locations.ids), @@ -393,11 +383,11 @@ class MultiLevelMrp(models.TransientModel): ] @api.model - def _out_stock_moves_domain(self, mrp_product): + def _out_stock_moves_domain(self, product_mrp_area): locations = self.env['stock.location'].search( - [('id', 'child_of', mrp_product.mrp_area_id.location_id.id)]) + [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)]) return [ - ('product_id', '=', mrp_product.product_id.id), + ('product_id', '=', product_mrp_area.product_id.id), ('state', 'not in', ['done', 'cancel']), ('product_qty', '>', 0.00), ('location_id', 'in', locations.ids), @@ -405,34 +395,34 @@ class MultiLevelMrp(models.TransientModel): ] @api.model - def _init_mrp_move_from_stock_move(self, mrp_product): + def _init_mrp_move_from_stock_move(self, product_mrp_area): # TODO: Should we exclude the quantity done from the moves? move_obj = self.env['stock.move'] mrp_move_obj = self.env['mrp.move'] - in_domain = self._in_stock_moves_domain(mrp_product) + in_domain = self._in_stock_moves_domain(product_mrp_area) in_moves = move_obj.search(in_domain) - out_domain = self._out_stock_moves_domain(mrp_product) + out_domain = self._out_stock_moves_domain(product_mrp_area) out_moves = move_obj.search(out_domain) if in_moves: for move in in_moves: move_data = self._prepare_mrp_move_data_from_stock_move( - mrp_product, move, direction='in') + product_mrp_area, move, direction='in') mrp_move_obj.create(move_data) if out_moves: for move in out_moves: move_data = self._prepare_mrp_move_data_from_stock_move( - mrp_product, move, direction='out') + product_mrp_area, move, direction='out') mrp_move_obj.create(move_data) return True @api.model - def _prepare_mrp_move_data_from_purchase_order(self, poline, mrp_product): + def _prepare_mrp_move_data_from_purchase_order(self, poline, product_mrp_area): mrp_date = date.today() if fields.Date.from_string(poline.date_planned) > date.today(): mrp_date = fields.Date.from_string(poline.date_planned) return { 'product_id': poline.product_id.id, - 'mrp_product_id': mrp_product.id, + 'product_mrp_area_id': product_mrp_area.id, 'production_id': None, 'purchase_order_id': poline.order_id.id, 'purchase_line_id': poline.id, @@ -453,9 +443,9 @@ class MultiLevelMrp(models.TransientModel): } @api.model - def _init_mrp_move_from_purchase_order(self, mrp_product): + def _init_mrp_move_from_purchase_order(self, product_mrp_area): location_ids = self.env['stock.location'].search( - [('id', 'child_of', mrp_product.mrp_area_id.location_id.id)]) + [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)]) picking_types = self.env['stock.picking.type'].search( [('default_location_dest_id', 'in', location_ids.ids)]) @@ -466,58 +456,63 @@ class MultiLevelMrp(models.TransientModel): po_lines = self.env['purchase.order.line'].search( [('order_id', 'in', orders.ids), ('product_qty', '>', 0.0), - ('product_id', '=', mrp_product.product_id.id)]) + ('product_id', '=', product_mrp_area.product_id.id)]) for line in po_lines: mrp_move_data = \ self._prepare_mrp_move_data_from_purchase_order( - line, mrp_product) + line, product_mrp_area) self.env['mrp.move'].create(mrp_move_data) @api.model - def _get_mrp_product_from_product_and_area(self, product, mrp_area): - return self.env['mrp.product'].search([ + def _get_product_mrp_area_from_product_and_area(self, product, mrp_area): + return self.env['product.mrp.area'].search([ ('product_id', '=', product.id), ('mrp_area_id', '=', mrp_area.id), ], limit=1) @api.model - def _init_mrp_move(self, mrp_product): - self._init_mrp_move_from_forecast(mrp_product) - self._init_mrp_move_from_stock_move(mrp_product) - self._init_mrp_move_from_purchase_order(mrp_product) + def _init_mrp_move(self, product_mrp_area): + self._init_mrp_move_from_forecast(product_mrp_area) + self._init_mrp_move_from_stock_move(product_mrp_area) + self._init_mrp_move_from_purchase_order(product_mrp_area) @api.model - def _exclude_from_mrp(self, mrp_area, product): + def _exclude_from_mrp(self, product, mrp_area): """ To extend with various logic where needed. """ - return product.mrp_exclude + product_mrp_area = self.env['product.mrp.area'].search( + [('product_id', '=', product.id), + ('mrp_area_id', '=', mrp_area.id)], limit=1) + if not product_mrp_area: + return True + return product_mrp_area.mrp_exclude @api.model def _mrp_initialisation(self): logger.info('START MRP INITIALISATION') mrp_areas = self.env['mrp.area'].search([]) - products = self.env['product.product'].search([ + product_mrp_areas = self.env['product.mrp.area'].search([ ('mrp_applicable', '=', True)]) init_counter = 0 for mrp_area in mrp_areas: - for product in products: - if self._exclude_from_mrp(mrp_area, product): + for product_mrp_area in product_mrp_areas.filtered( + lambda a: a.mrp_area_id == mrp_area): + if product_mrp_area.mrp_exclude: continue init_counter += 1 log_msg = 'MRP INIT: %s - %s ' % ( - init_counter, product.default_code) + init_counter, product_mrp_area.display_name) logger.info(log_msg) - mrp_product = self._init_mrp_product(product, mrp_area) - self._init_mrp_move(mrp_product) + self._init_mrp_move(product_mrp_area) logger.info('END MRP INITIALISATION') @api.model - def _init_mrp_move_grouped_demand(self, nbr_create, mrp_product): + def _init_mrp_move_grouped_demand(self, nbr_create, product_mrp_area): last_date = None last_qty = 0.00 - onhand = mrp_product.mrp_qty_available + onhand = product_mrp_area.qty_available move_ids = [] - for move in mrp_product.mrp_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( @@ -529,18 +524,18 @@ class MultiLevelMrp(models.TransientModel): datetime.strptime( move.mrp_date, '%Y-%m-%d')) \ > last_date+timedelta( - days=mrp_product.mrp_nbr_days): + days=product_mrp_area.mrp_nbr_days): if (onhand + last_qty + move.mrp_qty) \ - < mrp_product.mrp_minimum_stock \ + < product_mrp_area.mrp_minimum_stock \ or (onhand + last_qty) \ - < mrp_product.mrp_minimum_stock: + < product_mrp_area.mrp_minimum_stock: name = 'Grouped Demand for %d Days' % \ - mrp_product.mrp_nbr_days + product_mrp_area.mrp_nbr_days qtytoorder = \ - mrp_product.mrp_minimum_stock - \ - mrp_product - last_qty + product_mrp_area.mrp_minimum_stock - \ + product_mrp_area - last_qty cm = self.create_move( - mrp_product_id=mrp_product.id, + product_mrp_area_id=product_mrp_area.id, mrp_date=last_date, mrp_qty=qtytoorder, name=name) @@ -550,9 +545,9 @@ class MultiLevelMrp(models.TransientModel): last_qty = 0.00 nbr_create += 1 if (onhand + last_qty + move.mrp_qty) < \ - mrp_product.mrp_minimum_stock or \ + product_mrp_area.mrp_minimum_stock or \ (onhand + last_qty) < \ - mrp_product.mrp_minimum_stock: + product_mrp_area.mrp_minimum_stock: if last_date is None: last_date = datetime.date( datetime.strptime(move.mrp_date, @@ -568,10 +563,10 @@ class MultiLevelMrp(models.TransientModel): if last_date is not None and last_qty != 0.00: name = 'Grouped Demand for %d Days' % \ - (mrp_product.mrp_nbr_days, ) - qtytoorder = mrp_product.mrp_minimum_stock - onhand - last_qty + (product_mrp_area.mrp_nbr_days, ) + qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty cm = self.create_move( - mrp_product_id=mrp_product.id, mrp_date=last_date, + product_mrp_area_id=product_mrp_area.id, mrp_date=last_date, mrp_qty=qtytoorder, name=name) qty_ordered = cm['qty_ordered'] onhand += qty_ordered @@ -581,30 +576,31 @@ class MultiLevelMrp(models.TransientModel): @api.model def _mrp_calculation(self, mrp_lowest_llc): logger.info('START MRP CALCULATION') - mrp_product_obj = self.env['mrp.product'] + product_mrp_area_obj = self.env['product.mrp.area'] counter = 0 for mrp_area in self.env['mrp.area'].search([]): llc = 0 while mrp_lowest_llc > llc: - mrp_products = mrp_product_obj.search( - [('mrp_llc', '=', llc), + product_mrp_areas = product_mrp_area_obj.search( + [('product_id.llc', '=', llc), ('mrp_area_id', '=', mrp_area.id)]) llc += 1 - for mrp_product in mrp_products: + for product_mrp_area in product_mrp_areas: nbr_create = 0 - onhand = mrp_product.mrp_qty_available # TODO: unreserved? - if mrp_product.mrp_nbr_days == 0: + onhand = product_mrp_area.qty_available + # TODO: unreserved? + if product_mrp_area.mrp_nbr_days == 0: # todo: review ordering by date - for move in mrp_product.mrp_move_ids: + for move in product_mrp_area.mrp_move_ids: if move.mrp_action == 'none': if (onhand + move.mrp_qty) < \ - mrp_product.mrp_minimum_stock: + product_mrp_area.mrp_minimum_stock: qtytoorder = \ - mrp_product.mrp_minimum_stock - \ + product_mrp_area.mrp_minimum_stock - \ onhand - move.mrp_qty cm = self.create_move( - mrp_product_id=mrp_product, + product_mrp_area_id=product_mrp_area, mrp_date=move.mrp_date, mrp_qty=qtytoorder, name=move.name) qty_ordered = cm['qty_ordered'] @@ -615,13 +611,13 @@ class MultiLevelMrp(models.TransientModel): else: # TODO: review this nbr_create = self._init_mrp_move_grouped_demand( - nbr_create, mrp_product) + nbr_create, product_mrp_area) - if onhand < mrp_product.mrp_minimum_stock and \ + if onhand < product_mrp_area.mrp_minimum_stock and \ nbr_create == 0: - qtytoorder = mrp_product.mrp_minimum_stock - onhand + qtytoorder = product_mrp_area.mrp_minimum_stock - onhand cm = self.create_move( - mrp_product_id=mrp_product, + product_mrp_area_id=product_mrp_area, mrp_date=date.today(), mrp_qty=qtytoorder, name='Minimum Stock') @@ -636,64 +632,64 @@ class MultiLevelMrp(models.TransientModel): logger.info('END MRP CALCULATION') @api.model - def _get_demand_groups(self, mrp_product): + def _get_demand_groups(self, product_mrp_area): query = """ SELECT mrp_date, sum(mrp_qty) FROM mrp_move - WHERE mrp_product_id = %(mrp_product)s + WHERE product_mrp_area_id = %(mrp_product)s AND mrp_type = 'd' GROUP BY mrp_date """ params = { - 'mrp_product': mrp_product.id + 'mrp_product': product_mrp_area.id } return query, params @api.model - def _get_supply_groups(self, mrp_product): + def _get_supply_groups(self, product_mrp_area): query = """ SELECT mrp_date, sum(mrp_qty) FROM mrp_move - WHERE mrp_product_id = %(mrp_product)s + WHERE product_mrp_area_id = %(mrp_product)s AND mrp_type = 's' AND mrp_action = 'none' GROUP BY mrp_date """ params = { - 'mrp_product': mrp_product.id + 'mrp_product': product_mrp_area.id } return query, params @api.model - def _get_supply_action_groups(self, mrp_product): + def _get_supply_action_groups(self, product_mrp_area): exclude_mrp_actions = ['none', 'cancel'] query = """ SELECT mrp_date, sum(mrp_qty) FROM mrp_move - WHERE mrp_product_id = %(mrp_product)s + WHERE product_mrp_area_id = %(mrp_product)s AND mrp_qty <> 0.0 AND mrp_type = 's' AND mrp_action not in %(excluded_mrp_actions)s GROUP BY mrp_date """ params = { - 'mrp_product': mrp_product.id, + 'mrp_product': product_mrp_area.id, 'excluded_mrp_actions': tuple(exclude_mrp_actions,) } return query, params @api.model - def _init_mrp_inventory(self, mrp_product): + def _init_mrp_inventory(self, product_mrp_area): mrp_move_obj = self.env['mrp.move'] # Read Demand demand_qty_by_date = {} - query, params = self._get_demand_groups(mrp_product) + query, params = self._get_demand_groups(product_mrp_area) self.env.cr.execute(query, params) for mrp_date, qty in self.env.cr.fetchall(): demand_qty_by_date[mrp_date] = qty # Read Supply supply_qty_by_date = {} - query, params = self._get_supply_groups(mrp_product) + query, params = self._get_supply_groups(product_mrp_area) self.env.cr.execute(query, params) for mrp_date, qty in self.env.cr.fetchall(): supply_qty_by_date[mrp_date] = qty @@ -701,19 +697,22 @@ class MultiLevelMrp(models.TransientModel): # TODO: if we remove cancel take it into account here, # TODO: as well as mrp_type ('r'). supply_actions_qty_by_date = {} - query, params = self._get_supply_action_groups(mrp_product) + query, params = self._get_supply_action_groups(product_mrp_area) self.env.cr.execute(query, params) for mrp_date, qty in self.env.cr.fetchall(): supply_actions_qty_by_date[mrp_date] = qty # Dates mrp_dates = set(mrp_move_obj.search([ - ('mrp_product_id', '=', mrp_product.id)], + ('product_mrp_area_id', '=', product_mrp_area.id)], order='mrp_date').mapped('mrp_date')) - - on_hand_qty = mrp_product.current_qty_available # TODO: unreserved? + on_hand_qty = product_mrp_area.product_id.with_context( + location=product_mrp_area.mrp_area_id.location_id.id + )._product_available()[ + product_mrp_area.product_id.id]['qty_available'] + # TODO: unreserved? for mdt in sorted(mrp_dates): mrp_inventory_data = { - 'mrp_product_id': mrp_product.id, + 'product_mrp_area_id': product_mrp_area.id, 'date': mdt, } demand_qty = 0.0 @@ -736,32 +735,31 @@ class MultiLevelMrp(models.TransientModel): @api.model def _mrp_final_process(self): logger.info('START MRP FINAL PROCESS') - mrp_product_ids = self.env['mrp.product'].search([ - ('mrp_llc', '<', 9999), - ('mrp_area_id', '!=', False)]) + product_mrp_area_ids = self.env['product.mrp.area'].search([ + ('product_id.llc', '<', 9999)]) - for mrp_product in mrp_product_ids: + for product_mrp_area in product_mrp_area_ids: # Build the time-phased inventory - self._init_mrp_inventory(mrp_product) + self._init_mrp_inventory(product_mrp_area) # Complete info on mrp_move (running availability and nbr actions) - qoh = mrp_product.mrp_qty_available + qoh = product_mrp_area.qty_available moves = self.env['mrp.move'].search([ - ('mrp_product_id', '=', mrp_product.id)], + ('product_mrp_area_id', '=', product_mrp_area.id)], order='mrp_date, mrp_type desc, id') for move in moves: qoh = qoh + move.mrp_qty move.running_availability = qoh - nbr_actions = mrp_product.mrp_move_ids.filtered( + 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: - mrp_product.write({ + product_mrp_area.write({ 'nbr_mrp_actions': len(nbr_actions), 'nbr_mrp_actions_4w': len(nbr_actions_4w), }) diff --git a/mrp_multi_level/wizards/mrp_multi_level_view.xml b/mrp_multi_level/wizards/mrp_multi_level_views.xml similarity index 100% rename from mrp_multi_level/wizards/mrp_multi_level_view.xml rename to mrp_multi_level/wizards/mrp_multi_level_views.xml From 17b1a81f4c399294db9fc770c251cb1d789225db Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Tue, 20 Nov 2018 10:10:32 +0100 Subject: [PATCH 2/3] [11.0] mrp_multi_level: * fix api.depends fields fro main supplier. * fix ordering and missing demo file in manifest. * Update README. * fix action_view* methods. * readd hook to exclude in mrp initialization * fix computation of qty available (it was considering several times sub-locations). * Remove contraint for outoing and incoming moves to be moved in/outside the company, they can be internal transfers. * mrp.moves visible with technical settings. * Show product and allow to search by it in mrp.inventory. --- mrp_multi_level/README.rst | 28 +++++- mrp_multi_level/__manifest__.py | 4 +- mrp_multi_level/models/mrp_inventory.py | 8 +- mrp_multi_level/models/product_mrp_area.py | 52 +++++------ mrp_multi_level/models/product_product.py | 12 +-- mrp_multi_level/models/product_template.py | 7 +- mrp_multi_level/readme/CONFIGURE.rst | 13 ++- mrp_multi_level/readme/HISTORY.rst | 11 +++ mrp_multi_level/readme/USAGE.rst | 4 +- mrp_multi_level/static/description/index.html | 88 ++++++++++++------- mrp_multi_level/tests/test_mrp_multi_level.py | 8 +- mrp_multi_level/views/mrp_inventory_views.xml | 5 +- .../views/product_mrp_area_views.xml | 28 +++++- mrp_multi_level/wizards/mrp_multi_level.py | 15 ++-- 14 files changed, 184 insertions(+), 99 deletions(-) diff --git a/mrp_multi_level/README.rst b/mrp_multi_level/README.rst index bbbf73e0c..1b79418f4 100644 --- a/mrp_multi_level/README.rst +++ b/mrp_multi_level/README.rst @@ -50,20 +50,29 @@ Key Features Configuration ============= -* Go to *Manufacturing > MRP > MRP Area* and define or edit any existing area. - You can specify the working hours for every area. +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 +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Go to *Manufacturing > Master Data > Product MRP Area Parameters* and set + the MRP parameters for a given product and area. Usage ===== To manually run the MRP scheduler: -#. Go to *Manufacturing > MRP > Run MRP Multi Level*. +#. Go to *Manufacturing > Operations > Run MRP Multi Level*. #. On the wizard click *Run MRP*. To launch replenishment orders (moves, purchases, production orders...): -#. Go to *Manufacturing > MRP > MRP Inventory*. +#. Go to *Manufacturing > Operations > MRP Inventory*. #. Filter with *To procure*. #. Select multiple records and click on *Action > Procure* or click the right hand side gears in any record. @@ -78,6 +87,17 @@ Known issues / Roadmap Changelog ========= +11.0.2.0.0 (2018-11-20) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [REW] Refactor MRP Area. + (`#322 `_): + + * MRP product concept dropped in favor of *Product MRP Area Parameters*. + This allow to set different MRP parameters for the same product in + different areas. + * Menu items reordering. + 11.0.1.1.0 (2018-08-30) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mrp_multi_level/__manifest__.py b/mrp_multi_level/__manifest__.py index 9c7b597e0..f32d72a20 100644 --- a/mrp_multi_level/__manifest__.py +++ b/mrp_multi_level/__manifest__.py @@ -38,9 +38,9 @@ 'demo': [ 'demo/product_category_demo.xml', 'demo/product_product_demo.xml', - 'demo/product_product_demo.xml', - 'demo/product_mrp_area_demo.xml', + 'demo/res_partner_demo.xml', 'demo/product_supplierinfo_demo.xml', + 'demo/product_mrp_area_demo.xml', 'demo/mrp_bom_demo.xml', 'demo/initial_on_hand_demo.xml', ], diff --git a/mrp_multi_level/models/mrp_inventory.py b/mrp_multi_level/models/mrp_inventory.py index e8a36adfb..64f472888 100644 --- a/mrp_multi_level/models/mrp_inventory.py +++ b/mrp_multi_level/models/mrp_inventory.py @@ -17,7 +17,6 @@ class MrpInventory(models.Model): # TODO: name to pass to procurements? # TODO: compute procurement_date to pass to the wizard? not needed for # PO at least. Check for MO and moves - # TODO: substract qty already procured. # TODO: show a LT based on the procure method? mrp_area_id = fields.Many2one( @@ -25,9 +24,14 @@ class MrpInventory(models.Model): related='product_mrp_area_id.mrp_area_id', store=True, ) product_mrp_area_id = fields.Many2one( - comodel_name='product.mrp.area', string='Product', + comodel_name='product.mrp.area', string='Product Parameters', index=True, ) + product_id = fields.Many2one( + comodel_name='product.product', + related='product_mrp_area_id.product_id', + store=True, + ) uom_id = fields.Many2one( comodel_name='product.uom', string='Product UoM', compute='_compute_uom_id', diff --git a/mrp_multi_level/models/product_mrp_area.py b/mrp_multi_level/models/product_mrp_area.py index 780edd4ab..682b4bd2e 100644 --- a/mrp_multi_level/models/product_mrp_area.py +++ b/mrp_multi_level/models/product_mrp_area.py @@ -11,19 +11,21 @@ class ProductMRPArea(models.Model): _description = 'Product MRP Area' active = fields.Boolean(default=True) - mrp_area_id = fields.Many2one('mrp.area', - required=True, - ) - product_id = fields.Many2one('product.product', - required=True, - string='Product', - ) - product_tmpl_id = fields.Many2one('product.template', - readonly=True, - related='product_id.product_tmpl_id', - store=True, - ) - + mrp_area_id = fields.Many2one( + comodel_name='mrp.area', + required=True, + ) + product_id = fields.Many2one( + comodel_name='product.product', + required=True, + string='Product', + ) + product_tmpl_id = fields.Many2one( + comodel_name='product.template', + readonly=True, + related='product_id.product_tmpl_id', + store=True, + ) # TODO: applicable and exclude... redundant?? mrp_applicable = fields.Boolean(string='MRP Applicable') mrp_exclude = fields.Boolean(string='Exclude from MRP') @@ -71,10 +73,11 @@ class ProductMRPArea(models.Model): qty_available = fields.Float('Quantity Available', compute='_compute_qty_available') - mrp_move_ids = fields.One2many(comodel_name='mrp.move', - inverse_name='product_mrp_area_id', - readonly=True, - ) + mrp_move_ids = fields.One2many( + comodel_name='mrp.move', + inverse_name='product_mrp_area_id', + readonly=True, + ) _sql_constraints = [ ('product_mrp_area_uniq', 'unique(product_id, mrp_area_id)', 'The product/MRP Area parameters combination must be unique.'), @@ -89,17 +92,9 @@ class ProductMRPArea(models.Model): @api.multi def _compute_qty_available(self): for rec in self: - qty_available = 0.0 - product_obj = self.env['product.product'] # TODO: move mrp_qty_available computation, maybe unreserved?? - location_ids = self.env['stock.location'].search( - [('id', 'child_of', - rec.mrp_area_id.location_id.id)]) - for location in location_ids: - product_l = product_obj.with_context( - {'location': location.id}).browse(rec.product_id.id) - qty_available += product_l.qty_available - rec.qty_available = qty_available + rec.qty_available = rec.product_id.with_context( + {'location': rec.mrp_area_id.location_id.id}).qty_available @api.multi def _compute_supply_method(self): @@ -115,7 +110,8 @@ class ProductMRPArea(models.Model): rec.supply_method = rule.action if rule else 'none' @api.multi - @api.depends('supply_method') + @api.depends('supply_method', 'product_id.route_ids', + 'product_id.seller_ids') def _compute_main_supplier(self): """Simplified and similar to procurement.rule logic.""" for rec in self.filtered(lambda r: r.supply_method == 'buy'): diff --git a/mrp_multi_level/models/product_product.py b/mrp_multi_level/models/product_product.py index f0ed768c8..e20078512 100644 --- a/mrp_multi_level/models/product_product.py +++ b/mrp_multi_level/models/product_product.py @@ -28,7 +28,8 @@ class Product(models.Model): mrp_area_count = fields.Integer( string='MRP Area Parameter Count', readonly=True, - compute='_compute_mrp_area_count') + compute='_compute_mrp_area_count', + ) @api.multi def _compute_mrp_area_count(self): @@ -40,12 +41,11 @@ class Product(models.Model): self.ensure_one() action = self.env.ref('mrp_multi_level.product_mrp_area_action') result = action.read()[0] - product_ids = self.ids - if len(product_ids) > 1: - result['domain'] = [('product_id', 'in', product_ids)] + area_ids = self.mrp_area_ids.ids + if self.mrp_area_count != 1: + result['domain'] = [('id', 'in', area_ids)] else: res = self.env.ref('mrp_multi_level.product_mrp_area_form', False) result['views'] = [(res and res.id or False, 'form')] - result['res_id'] = product_ids[0] - result['context'] = {'default_product_id': product_ids[0]} + result['res_id'] = area_ids[0] return result diff --git a/mrp_multi_level/models/product_template.py b/mrp_multi_level/models/product_template.py index 67b8e76be..48e43ba97 100644 --- a/mrp_multi_level/models/product_template.py +++ b/mrp_multi_level/models/product_template.py @@ -15,7 +15,8 @@ class ProductTemplate(models.Model): mrp_area_count = fields.Integer( string='MRP Area Parameter Count', readonly=True, - compute='_compute_mrp_area_count') + compute='_compute_mrp_area_count', + ) @api.multi def _compute_mrp_area_count(self): @@ -29,12 +30,10 @@ class ProductTemplate(models.Model): result = action.read()[0] mrp_area_ids = self.with_context( active_test=False).mrp_area_ids.ids - if len(mrp_area_ids) > 1: + if len(mrp_area_ids) != 1: result['domain'] = [('id', 'in', mrp_area_ids)] else: res = self.env.ref('mrp_multi_level.product_mrp_area_form', False) result['views'] = [(res and res.id or False, 'form')] result['res_id'] = mrp_area_ids[0] - result['context'] = { - 'default_product_id': self.product_variant_ids[0].id} return result diff --git a/mrp_multi_level/readme/CONFIGURE.rst b/mrp_multi_level/readme/CONFIGURE.rst index 7237643b8..3825da541 100644 --- a/mrp_multi_level/readme/CONFIGURE.rst +++ b/mrp_multi_level/readme/CONFIGURE.rst @@ -1,2 +1,11 @@ -* Go to *Manufacturing > MRP > MRP Area* and define or edit any existing area. - You can specify the working hours for every area. +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 +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Go to *Manufacturing > Master Data > Product MRP Area Parameters* and set + the MRP parameters for a given product and area. diff --git a/mrp_multi_level/readme/HISTORY.rst b/mrp_multi_level/readme/HISTORY.rst index 01bd8c4d1..03ff5468e 100644 --- a/mrp_multi_level/readme/HISTORY.rst +++ b/mrp_multi_level/readme/HISTORY.rst @@ -1,3 +1,14 @@ +11.0.2.0.0 (2018-11-20) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [REW] Refactor MRP Area. + (`#322 `_): + + * MRP product concept dropped in favor of *Product MRP Area Parameters*. + This allow to set different MRP parameters for the same product in + different areas. + * Menu items reordering. + 11.0.1.1.0 (2018-08-30) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mrp_multi_level/readme/USAGE.rst b/mrp_multi_level/readme/USAGE.rst index fc56ab031..6e28dce46 100644 --- a/mrp_multi_level/readme/USAGE.rst +++ b/mrp_multi_level/readme/USAGE.rst @@ -1,11 +1,11 @@ To manually run the MRP scheduler: -#. Go to *Manufacturing > MRP > Run MRP Multi Level*. +#. Go to *Manufacturing > Operations > Run MRP Multi Level*. #. On the wizard click *Run MRP*. To launch replenishment orders (moves, purchases, production orders...): -#. Go to *Manufacturing > MRP > MRP Inventory*. +#. Go to *Manufacturing > Operations > MRP Inventory*. #. Filter with *To procure*. #. Select multiple records and click on *Action > Procure* or click the right hand side gears in any record. diff --git a/mrp_multi_level/static/description/index.html b/mrp_multi_level/static/description/index.html index 284ec77a8..8fc23d952 100644 --- a/mrp_multi_level/static/description/index.html +++ b/mrp_multi_level/static/description/index.html @@ -386,41 +386,56 @@ and explodes this down to the lowest level.

Table of contents

-

Configuration

+

Configuration

+
+

MRP Areas

    -
  • Go to Manufacturing > MRP > MRP Area and define or edit any existing area. -You can specify the working hours for every area.
  • +
  • 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

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

Usage

+

Usage

To manually run the MRP scheduler:

    -
  1. Go to Manufacturing > MRP > Run MRP Multi Level.
  2. +
  3. Go to Manufacturing > Operations > Run MRP Multi Level.
  4. On the wizard click Run MRP.

To launch replenishment orders (moves, purchases, production orders…):

    -
  1. Go to Manufacturing > MRP > MRP Inventory.
  2. +
  3. Go to Manufacturing > Operations > MRP Inventory.
  4. Filter with To procure.
  5. Select multiple records and click on Action > Procure or click the right hand side gears in any record.
  6. @@ -428,23 +443,36 @@ hand side gears in any record.
-

Known issues / Roadmap

+

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

+

Changelog

-

11.0.1.1.0 (2018-08-30)

+

11.0.2.0.0 (2018-11-20)

+
    +
  • [REW] Refactor MRP Area. +(#322):
      +
    • MRP product concept dropped in favor of Product MRP Area Parameters. +This allow to set different MRP parameters for the same product in +different areas.
    • +
    • Menu items reordering.
    • +
    +
  • +
+
+
+

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)
  • @@ -453,15 +481,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 @@ -469,16 +497,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 1d638fd65..c417a1fe1 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -23,7 +23,7 @@ class TestMrpMultiLevel(SavepointCase): cls.estimate_obj = cls.env['stock.demand.estimate'] cls.mrp_multi_level_wiz = cls.env['mrp.multi.level'] cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure'] - cls.mrp_inventory_obj = cls.env['mrp.inventory'] + cls.mrp_inventory_obj = cls.env['mrp.inventory'] cls.mrp_move_obj = cls.env['mrp.move'] cls.fp_1 = cls.env.ref('mrp_multi_level.product_product_fp_1') @@ -417,9 +417,9 @@ class TestMrpMultiLevel(SavepointCase): 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) - ]) + # 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 diff --git a/mrp_multi_level/views/mrp_inventory_views.xml b/mrp_multi_level/views/mrp_inventory_views.xml index 484df5d7b..0e804683e 100644 --- a/mrp_multi_level/views/mrp_inventory_views.xml +++ b/mrp_multi_level/views/mrp_inventory_views.xml @@ -11,6 +11,7 @@ + @@ -35,7 +36,7 @@ - + @@ -84,7 +85,7 @@ - + diff --git a/mrp_multi_level/views/product_mrp_area_views.xml b/mrp_multi_level/views/product_mrp_area_views.xml index 8ab78a361..aeb2eed4d 100644 --- a/mrp_multi_level/views/product_mrp_area_views.xml +++ b/mrp_multi_level/views/product_mrp_area_views.xml @@ -59,6 +59,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -70,10 +92,8 @@ search - - - - + + diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py index 16269844f..5ace1b235 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -70,12 +70,6 @@ class MultiLevelMrp(models.TransientModel): @api.model def _prepare_mrp_move_data_from_stock_move( self, product_mrp_area, move, direction='in'): - if not((move.location_id.usage == 'internal' and - move.location_dest_id.usage != 'internal') - or (move.location_id.usage != 'internal' and - move.location_dest_id.usage == 'internal')): - # TODO: not sure about this 'if'... - return {} if direction == 'out': mrp_type = 'd' product_qty = -move.product_qty @@ -416,7 +410,8 @@ class MultiLevelMrp(models.TransientModel): return True @api.model - def _prepare_mrp_move_data_from_purchase_order(self, poline, product_mrp_area): + def _prepare_mrp_move_data_from_purchase_order( + self, poline, product_mrp_area): mrp_date = date.today() if fields.Date.from_string(poline.date_planned) > date.today(): mrp_date = fields.Date.from_string(poline.date_planned) @@ -497,7 +492,8 @@ class MultiLevelMrp(models.TransientModel): for mrp_area in mrp_areas: for product_mrp_area in product_mrp_areas.filtered( lambda a: a.mrp_area_id == mrp_area): - if product_mrp_area.mrp_exclude: + if self._exclude_from_mrp( + product_mrp_area.product_id, mrp_area): continue init_counter += 1 log_msg = 'MRP INIT: %s - %s ' % ( @@ -615,7 +611,8 @@ class MultiLevelMrp(models.TransientModel): if onhand < product_mrp_area.mrp_minimum_stock and \ nbr_create == 0: - qtytoorder = product_mrp_area.mrp_minimum_stock - onhand + qtytoorder = \ + product_mrp_area.mrp_minimum_stock - onhand cm = self.create_move( product_mrp_area_id=product_mrp_area, mrp_date=date.today(), From 02b1f97699fca541b5422af9c06d9ac891447678 Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Wed, 28 Nov 2018 12:56:38 +0100 Subject: [PATCH 3/3] Fixes, and add migration script --- .../migrations/11.0.2.0.0/post-migration.py | 50 +++++++++++++++++++ mrp_multi_level/models/product_product.py | 10 +++- mrp_multi_level/models/product_template.py | 12 ++++- .../views/product_mrp_area_views.xml | 46 +++++++++-------- .../views/product_product_views.xml | 2 +- .../views/product_template_views.xml | 8 +-- mrp_multi_level/wizards/mrp_multi_level.py | 28 +++++------ 7 files changed, 113 insertions(+), 43 deletions(-) create mode 100644 mrp_multi_level/migrations/11.0.2.0.0/post-migration.py diff --git a/mrp_multi_level/migrations/11.0.2.0.0/post-migration.py b/mrp_multi_level/migrations/11.0.2.0.0/post-migration.py new file mode 100644 index 000000000..6bf508127 --- /dev/null +++ b/mrp_multi_level/migrations/11.0.2.0.0/post-migration.py @@ -0,0 +1,50 @@ +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import logging +from odoo import api, SUPERUSER_ID + +_logger = logging.getLogger(__name__) + +__name__ = "Upgrade to 11.0.2.0.0" + + +def _migrate_product_to_product_mrp_area(env): + _logger.info("Migrating product parameters to Product MRP Areas") + env.cr.execute(""" + SELECT DISTINCT mrp_area.id, pr.id, pr.mrp_applicable, pr.mrp_exclude, + pr.mrp_inspection_delay, pr.mrp_maximum_order_qty, + pr.mrp_minimum_order_qty, pr.mrp_minimum_stock, pr.mrp_nbr_days, + pr.mrp_qty_multiple, pr.mrp_transit_delay, pr.mrp_verified, pr.active + FROM product_product AS pr + CROSS JOIN mrp_area + LEFT JOIN product_template AS pt + ON pt.id = pr.product_tmpl_id + WHERE pr.mrp_exclude = False + AND pt.type = 'product' + """) + product_mrp_area_model = env['product.mrp.area'] + for mrp_area_id, product_id, mrp_applicable, mrp_exclude,\ + mrp_inspection_delay, mrp_maximum_order_qty, mrp_minimum_order_qty, \ + mrp_minimum_stock, mrp_nbr_days, mrp_qty_multiple, mrp_transit_delay,\ + mrp_verified, active in env.cr.fetchall(): + product_mrp_area_model.create({ + 'mrp_area_id': mrp_area_id, + 'product_id': product_id, + 'mrp_applicable': mrp_applicable, + 'mrp_exclude': mrp_exclude, + 'mrp_inspection_delay': mrp_inspection_delay, + 'mrp_maximum_order_qty': mrp_maximum_order_qty, + 'mrp_minimum_order_qty': mrp_minimum_order_qty, + 'mrp_minimum_stock': mrp_minimum_stock, + 'mrp_nbr_days': mrp_nbr_days, + 'mrp_qty_multiple': mrp_qty_multiple, + 'mrp_transit_delay': mrp_transit_delay, + 'mrp_verified': mrp_verified, + 'active': active, + }) + + +def migrate(cr, version): + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + _migrate_product_to_product_mrp_area(env) diff --git a/mrp_multi_level/models/product_product.py b/mrp_multi_level/models/product_product.py index e20078512..1f003bf11 100644 --- a/mrp_multi_level/models/product_product.py +++ b/mrp_multi_level/models/product_product.py @@ -1,7 +1,7 @@ # Copyright 2016 Ucamco - Wim Audenaert # Copyright 2016-18 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - +import ast from odoo import api, fields, models @@ -41,11 +41,19 @@ class Product(models.Model): self.ensure_one() action = self.env.ref('mrp_multi_level.product_mrp_area_action') result = action.read()[0] + ctx = ast.literal_eval(result.get('context')) + if not ctx: + ctx = {} + mrp_areas = self.env['mrp.area'].search([]) + if len(mrp_areas) == 1: + ctx.update({'default_mrp_area_id': mrp_areas[0].id}) area_ids = self.mrp_area_ids.ids + ctx.update({'default_product_id': self.id}) if self.mrp_area_count != 1: result['domain'] = [('id', 'in', area_ids)] else: res = self.env.ref('mrp_multi_level.product_mrp_area_form', False) result['views'] = [(res and res.id or False, 'form')] result['res_id'] = area_ids[0] + result['context'] = ctx return result diff --git a/mrp_multi_level/models/product_template.py b/mrp_multi_level/models/product_template.py index 48e43ba97..ed4ccaead 100644 --- a/mrp_multi_level/models/product_template.py +++ b/mrp_multi_level/models/product_template.py @@ -1,6 +1,6 @@ # Copyright 2018 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - +import ast from odoo import api, fields, models @@ -28,12 +28,22 @@ class ProductTemplate(models.Model): self.ensure_one() action = self.env.ref('mrp_multi_level.product_mrp_area_action') result = action.read()[0] + ctx = ast.literal_eval(result.get('context')) + mrp_areas = self.env['mrp.area'].search([]) + if 'context' not in result: + result['context'] = {} + if len(mrp_areas) == 1: + ctx.update({'default_mrp_area_id': mrp_areas[0].id}) mrp_area_ids = self.with_context( active_test=False).mrp_area_ids.ids + if len(self.product_variant_ids) == 1: + variant = self.product_variant_ids[0] + ctx.update({'default_product_id': variant.id}) if len(mrp_area_ids) != 1: result['domain'] = [('id', 'in', mrp_area_ids)] else: res = self.env.ref('mrp_multi_level.product_mrp_area_form', False) result['views'] = [(res and res.id or False, 'form')] result['res_id'] = mrp_area_ids[0] + result['context'] = ctx return result diff --git a/mrp_multi_level/views/product_mrp_area_views.xml b/mrp_multi_level/views/product_mrp_area_views.xml index aeb2eed4d..11c8d02a0 100644 --- a/mrp_multi_level/views/product_mrp_area_views.xml +++ b/mrp_multi_level/views/product_mrp_area_views.xml @@ -59,28 +59,30 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mrp_multi_level/views/product_product_views.xml b/mrp_multi_level/views/product_product_views.xml index 65014f376..ad24d4863 100644 --- a/mrp_multi_level/views/product_product_views.xml +++ b/mrp_multi_level/views/product_product_views.xml @@ -1,7 +1,7 @@ - + view.product.mrp.area.product.form product.product diff --git a/mrp_multi_level/views/product_template_views.xml b/mrp_multi_level/views/product_template_views.xml index 843413d55..1a5e9ddd5 100644 --- a/mrp_multi_level/views/product_template_views.xml +++ b/mrp_multi_level/views/product_template_views.xml @@ -8,10 +8,10 @@

diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py index 5ace1b235..2143d0ca5 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -531,7 +531,7 @@ class MultiLevelMrp(models.TransientModel): product_mrp_area.mrp_minimum_stock - \ product_mrp_area - last_qty cm = self.create_move( - product_mrp_area_id=product_mrp_area.id, + product_mrp_area_id=product_mrp_area, mrp_date=last_date, mrp_qty=qtytoorder, name=name) @@ -562,7 +562,7 @@ class MultiLevelMrp(models.TransientModel): (product_mrp_area.mrp_nbr_days, ) qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty cm = self.create_move( - product_mrp_area_id=product_mrp_area.id, mrp_date=last_date, + product_mrp_area_id=product_mrp_area, mrp_date=last_date, mrp_qty=qtytoorder, name=name) qty_ordered = cm['qty_ordered'] onhand += qty_ordered @@ -748,18 +748,18 @@ class MultiLevelMrp(models.TransientModel): for move in moves: qoh = qoh + move.mrp_qty move.running_availability = qoh - - 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), - }) + # 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') @api.multi