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
-
+
+
+
-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.
+
+
+
+Go to Manufacturing > Master Data > Product MRP Area Parameters and set
+the MRP parameters for a given product and area.
+
+
+
-
+
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.
@@ -428,23 +443,36 @@ hand side gears in any record.
-
+
The functionality related to field Nbr. Days in products is not
functional for the time being. Please, stay tuned to future updates.
-
+
-
+
+
+[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.
+
+
+
+
+
+
[FIX] Consider Qty Multiple on product to propose the quantity to procure.
(#297 )
-
-
+
+
[FIX] User and system locales doesn’t break MRP calculation.
(#290 )
@@ -453,15 +481,15 @@ as a related on MRP Areas.
(#290 )
-
-
+
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.
-
+
-
+
This module is maintained by the OCA.
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 @@
+ name="action_view_mrp_area_parameters"
+ class="oe_stat_button"
+ icon="fa-eject"
+ groups="mrp.group_mrp_user,rma.group_mrp_manager">
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