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