-
+
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 09e00db97..1a43a1025 100644
--- a/mrp_multi_level/tests/test_mrp_multi_level.py
+++ b/mrp_multi_level/tests/test_mrp_multi_level.py
@@ -28,6 +28,7 @@ class TestMrpMultiLevel(SavepointCase):
cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure']
cls.mrp_inventory_obj = cls.env['mrp.inventory']
cls.mrp_move_obj = cls.env['mrp.move']
+ cls.planned_order_obj = cls.env['mrp.planned.order']
cls.fp_1 = cls.env.ref('mrp_multi_level.product_product_fp_1')
cls.fp_2 = cls.env.ref('mrp_multi_level.product_product_fp_2')
@@ -322,38 +323,36 @@ class TestMrpMultiLevel(SavepointCase):
"""Tests for mrp moves generated."""
moves = self.mrp_move_obj.search([
('product_id', '=', self.pp_1.id),
- ('mrp_action', '=', 'none'),
])
self.assertEqual(len(moves), 3)
self.assertNotIn('s', moves.mapped('mrp_type'))
for move in moves:
- self.assertTrue(move.mrp_move_up_ids)
- if move.mrp_move_up_ids.product_mrp_area_id.product_id == \
+ self.assertTrue(move.planned_order_up_ids)
+ if move.planned_order_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.planned_order_up_ids.mrp_action, "manufacture")
self.assertEqual(move.mrp_qty, -200.0)
- elif move.mrp_move_up_ids.product_mrp_area_id.product_id == \
+ elif move.planned_order_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')
+ self.assertEqual(
+ move.planned_order_up_ids.mrp_action, "manufacture")
if move.mrp_date == self.date_5:
self.assertEqual(move.mrp_qty, -90.0)
elif move.mrp_date == self.date_8:
self.assertEqual(move.mrp_qty, -72.0)
# Check actions:
- moves = self.mrp_move_obj.search([
+ planned_orders = self.planned_order_obj.search([
('product_id', '=', self.pp_1.id),
- ('mrp_action', '!=', 'none'),
])
- self.assertEqual(len(moves), 3)
- for move in moves:
- self.assertEqual(move.mrp_action, 'po')
- self.assertEqual(move.mrp_type, 's')
+ self.assertEqual(len(planned_orders), 3)
+ for plan in planned_orders:
+ self.assertEqual(plan.mrp_action, 'buy')
# Check PP-2 PO being accounted:
po_move = self.mrp_move_obj.search([
('product_id', '=', self.pp_2.id),
- ('mrp_action', '=', 'none'),
('mrp_type', '=', 's'),
])
self.assertEqual(len(po_move), 1)
@@ -452,16 +451,15 @@ class TestMrpMultiLevel(SavepointCase):
self.assertEqual(pp_2_line_4.demand_qty, 48.0)
self.assertEqual(pp_2_line_4.to_procure, 48.0)
- def test_05_moves_extra_info(self):
- """Test running availability and actions counters computation on
- mrp moves."""
+ def test_05_planned_availability(self):
+ """Test planned availability computation."""
# Running availability for PP-1:
- moves = self.mrp_move_obj.search([
+ invs = self.mrp_inventory_obj.search([
('product_id', '=', self.pp_1.id)],
- order='mrp_date, mrp_type desc, id')
- self.assertEqual(len(moves), 6)
- expected = [200.0, 290.0, 90.0, 0.0, 72.0, 0.0]
- self.assertEqual(moves.mapped('running_availability'), expected)
+ order='date')
+ self.assertEqual(len(invs), 2)
+ expected = [0.0, 0.0] # No grouping, lot size nor safety stock.
+ self.assertEqual(invs.mapped('running_availability'), expected)
def test_06_demand_estimates(self):
"""Tests demand estimates integration."""
@@ -480,8 +478,14 @@ class TestMrpMultiLevel(SavepointCase):
self.assertIn(-30.0, quantities) # 210 a week => 30.0 dayly:
self.assertIn(-40.0, quantities) # 280 a week => 40.0 dayly:
self.assertIn(-50.0, quantities) # 350 a week => 50.0 dayly:
- actions = moves.filtered(lambda m: m.mrp_action == 'po')
- self.assertEqual(len(actions), 18)
+ plans = self.planned_order_obj.search([
+ ('product_id', '=', self.prod_test.id),
+ ('mrp_area_id', '=', self.mrp_area.id),
+ ])
+ action = list(set(plans.mapped("mrp_action")))
+ self.assertEqual(len(action), 1)
+ self.assertEqual(action[0], "buy")
+ self.assertEqual(len(plans), 18)
inventories = self.mrp_inventory_obj.search([
('mrp_area_id', '=', self.secondary_area.id)])
self.assertEqual(len(inventories), 18)
@@ -516,13 +520,12 @@ class TestMrpMultiLevel(SavepointCase):
mrp_inv_max = self.mrp_inventory_obj.search([
('product_mrp_area_id.product_id', '=', self.prod_max.id)])
self.assertEqual(mrp_inv_max.to_procure, 150)
- moves = self.mrp_move_obj.search([
+ plans = self.planned_order_obj.search([
('product_id', '=', self.prod_max.id),
- ('mrp_action', '!=', 'none'),
])
- self.assertEqual(len(moves), 2)
- self.assertIn(100.0, moves.mapped('mrp_qty'))
- self.assertIn(50.0, moves.mapped('mrp_qty'))
+ self.assertEqual(len(plans), 2)
+ self.assertIn(100.0, plans.mapped('mrp_qty'))
+ self.assertIn(50.0, plans.mapped('mrp_qty'))
# quantity multiple:
mrp_inv_multiple = self.mrp_inventory_obj.search([
('product_mrp_area_id.product_id', '=', self.prod_multiple.id)])
@@ -538,13 +541,16 @@ class TestMrpMultiLevel(SavepointCase):
('product_id', '=', self.prod_test.id),
('mrp_area_id', '=', self.secondary_area.id),
])
+ supply_plans = self.planned_order_obj.search([
+ ('product_id', '=', self.prod_test.id),
+ ('mrp_area_id', '=', self.secondary_area.id),
+ ])
# 3 weeks - 3 days in the past = 18 days of valid estimates:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == 'd')
self.assertEqual(len(moves_from_estimates), 18)
# 18 days of demand / 7 nbr_days = 2.57 => 3 supply moves expected.
- supply_moves = moves.filtered(lambda m: m.mrp_type == 's')
- self.assertEqual(len(supply_moves), 3)
- quantities = supply_moves.mapped('mrp_qty')
+ self.assertEqual(len(supply_plans), 3)
+ quantities = supply_plans.mapped('mrp_qty')
week_1_expected = sum(moves_from_estimates[0:7].mapped('mrp_qty'))
self.assertIn(abs(week_1_expected), quantities)
week_2_expected = sum(moves_from_estimates[7:14].mapped('mrp_qty'))
diff --git a/mrp_multi_level/views/mrp_area_views.xml b/mrp_multi_level/views/mrp_area_views.xml
index e887855ea..1478ff608 100644
--- a/mrp_multi_level/views/mrp_area_views.xml
+++ b/mrp_multi_level/views/mrp_area_views.xml
@@ -21,17 +21,28 @@
form
diff --git a/mrp_multi_level/views/mrp_inventory_views.xml b/mrp_multi_level/views/mrp_inventory_views.xml
index 0e804683e..e4a180ffb 100644
--- a/mrp_multi_level/views/mrp_inventory_views.xml
+++ b/mrp_multi_level/views/mrp_inventory_views.xml
@@ -1,7 +1,7 @@
-
+
mrp.inventory.form
mrp.inventory
form
@@ -29,7 +29,7 @@
-
+
mrp.inventory.tree
mrp.inventory
tree
@@ -49,6 +49,7 @@
name="%(mrp_multi_level.act_mrp_inventory_procure)d"
icon="fa-cogs" type="action"
attrs="{'invisible':[('to_procure','<=',0.0)]}"/>
+
@@ -78,7 +79,7 @@
-
+
mrp.inventory.search
mrp.inventory
search
@@ -108,7 +109,7 @@
-
+
MRP Inventory
mrp.inventory
ir.actions.act_window
diff --git a/mrp_multi_level/views/product_mrp_area_views.xml b/mrp_multi_level/views/product_mrp_area_views.xml
index 11c8d02a0..5580953a4 100644
--- a/mrp_multi_level/views/product_mrp_area_views.xml
+++ b/mrp_multi_level/views/product_mrp_area_views.xml
@@ -49,13 +49,15 @@
-
-
+
+
+
+
@@ -63,7 +65,6 @@
-
@@ -73,12 +74,21 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mrp_multi_level/wizards/mrp_inventory_procure.py b/mrp_multi_level/wizards/mrp_inventory_procure.py
index 57fe8df8c..0bf65ca2c 100644
--- a/mrp_multi_level/wizards/mrp_inventory_procure.py
+++ b/mrp_multi_level/wizards/mrp_inventory_procure.py
@@ -1,4 +1,4 @@
-# Copyright 2018 Eficent Business and IT Consulting Services S.L.
+# Copyright 2018-19 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
@@ -17,15 +17,16 @@ class MrpInventoryProcure(models.TransientModel):
)
@api.model
- def _prepare_item(self, mrp_inventory, qty_override=0.0):
+ def _prepare_item(self, planned_order):
return {
- 'qty': qty_override if qty_override else mrp_inventory.to_procure,
- 'uom_id': mrp_inventory.uom_id.id,
- 'date_planned': mrp_inventory.date,
- 'mrp_inventory_id': mrp_inventory.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,
+ 'planned_order_id': planned_order.id,
+ 'qty': planned_order.mrp_qty - planned_order.qty_released,
+ 'uom_id': planned_order.mrp_inventory_id.uom_id.id,
+ 'date_planned': planned_order.due_date,
+ 'mrp_inventory_id': planned_order.mrp_inventory_id.id,
+ 'product_id': planned_order.product_id.id,
+ 'warehouse_id': planned_order.mrp_area_id.warehouse_id.id,
+ 'location_id': planned_order.mrp_area_id.location_id.id,
}
@api.model
@@ -56,17 +57,9 @@ class MrpInventoryProcure(models.TransientModel):
assert active_model == 'mrp.inventory', 'Bad context propagation'
items = item_obj = self.env['mrp.inventory.procure.item']
- for line in mrp_inventory_obj.browse(mrp_inventory_ids):
- 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.product_mrp_area_id._adjust_qty_to_order(
- qty_to_order)
- items += item_obj.create(self._prepare_item(line, qty))
- qty_to_order -= qty
- else:
+ for line in mrp_inventory_obj.browse(mrp_inventory_ids).mapped(
+ 'planned_order_ids'):
+ if line.qty_released < line.mrp_qty:
items += item_obj.create(self._prepare_item(line))
res['item_ids'] = [(6, 0, items.ids)]
return res
@@ -90,11 +83,9 @@ class MrpInventoryProcure(models.TransientModel):
'INT: ' + str(self.env.user.login), # origin?
values
)
- item.mrp_inventory_id.to_procure -= \
- item.uom_id._compute_quantity(
- item.qty, item.product_id.uom_id)
+ item.planned_order_id.qty_released += item.qty
except UserError as error:
- errors.append(error.name)
+ errors.append(error.name)
if errors:
raise UserError('\n'.join(errors))
return {'type': 'ir.actions.act_window_close'}
@@ -117,6 +108,9 @@ class MrpInventoryProcureItem(models.TransientModel):
string='Mrp Inventory',
comodel_name='mrp.inventory',
)
+ planned_order_id = fields.Many2one(
+ comodel_name='mrp.planned.order',
+ )
product_id = fields.Many2one(
string='Product',
comodel_name='product.product',
diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py
index eccd80b8c..ec5204276 100644
--- a/mrp_multi_level/wizards/mrp_multi_level.py
+++ b/mrp_multi_level/wizards/mrp_multi_level.py
@@ -28,8 +28,7 @@ class MultiLevelMrp(models.TransientModel):
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', product_mrp_area.mrp_area_id.location_id.id)])
+ location_ids = product_mrp_area.mrp_area_id._get_locations()
for location in location_ids:
product_l = product_obj.with_context(
{'location': location.id}).browse(
@@ -63,13 +62,10 @@ class MultiLevelMrp(models.TransientModel):
'current_qty': -daily_qty,
'mrp_date': date,
'current_date': date,
- 'mrp_action': 'none',
'mrp_type': mrp_type,
- 'mrp_processed': False,
'mrp_origin': origin,
'mrp_order_number': None,
'parent_product_id': None,
- 'running_availability': 0.00,
'name': 'Forecast',
'state': 'confirmed',
}
@@ -127,39 +123,26 @@ class MultiLevelMrp(models.TransientModel):
'current_qty': product_qty,
'mrp_date': mrp_date,
'current_date': move.date_expected,
- 'mrp_action': 'none',
'mrp_type': mrp_type,
- 'mrp_processed': False,
'mrp_origin': origin,
'mrp_order_number': order_number,
'parent_product_id': parent_product_id,
- 'running_availability': 0.00,
'name': order_number,
'state': move.state,
}
@api.model
- def _prepare_mrp_move_data_supply(
- self, product_mrp_area, qty, mrp_date_supply, mrp_action_date,
- mrp_action, name):
+ def _prepare_planned_order_data(
+ self, product_mrp_area, qty, mrp_date_supply,
+ mrp_action_date, name
+ ):
return {
- '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,
- 'stock_move_id': None,
'mrp_qty': qty,
- 'current_qty': None,
- 'mrp_date': mrp_date_supply,
- 'mrp_action_date': mrp_action_date,
- 'current_date': None,
- 'mrp_action': mrp_action,
- 'mrp_type': 's',
- 'mrp_processed': False,
- 'mrp_origin': None,
- 'mrp_order_number': None,
- 'parent_product_id': None,
+ 'due_date': mrp_date_supply,
+ 'order_release_date': mrp_action_date,
+ 'mrp_action': product_mrp_area.supply_method,
+ 'qty_released': 0.0,
'name': 'Supply: ' + name,
}
@@ -184,9 +167,7 @@ class MultiLevelMrp(models.TransientModel):
'current_qty': None,
'mrp_date': mrp_date_demand_2,
'current_date': None,
- 'mrp_action': 'none',
'mrp_type': 'd',
- 'mrp_processed': False,
'mrp_origin': 'mrp',
'mrp_order_number': None,
'parent_product_id': bom.product_id.id,
@@ -198,82 +179,104 @@ class MultiLevelMrp(models.TransientModel):
}
@api.model
- def create_move(self, product_mrp_area_id, mrp_date, mrp_qty, name):
- self = self.with_context(auditlog_disabled=True)
-
- values = {}
+ def _get_action_and_supply_dates(self, product_mrp_area, mrp_date):
if not isinstance(mrp_date, date):
mrp_date = fields.Date.from_string(mrp_date)
- if product_mrp_area_id.supply_method == 'buy':
- # if product_mrp_area_id.purchase_requisition:
- # mrp_action = 'pr'
- # else:
- mrp_action = 'po'
- else:
- # TODO: consider 'none'...
- mrp_action = 'mo'
-
if mrp_date < date.today():
mrp_date_supply = date.today()
else:
mrp_date_supply = mrp_date
- calendar = product_mrp_area_id.mrp_area_id.calendar_id
- if calendar and product_mrp_area_id.mrp_lead_time:
+ calendar = product_mrp_area.mrp_area_id.calendar_id
+ if calendar and product_mrp_area.mrp_lead_time:
date_str = fields.Date.to_string(mrp_date)
dt = fields.Datetime.from_string(date_str)
res = calendar.plan_days(
- -1 * product_mrp_area_id.mrp_lead_time - 1, dt)
+ -1 * product_mrp_area.mrp_lead_time - 1, dt)
mrp_action_date = res.date()
else:
mrp_action_date = mrp_date - timedelta(
- days=product_mrp_area_id.mrp_lead_time)
+ days=product_mrp_area.mrp_lead_time)
+ return mrp_action_date, mrp_date_supply
- qty_ordered = 0.00
+ @api.model
+ def explode_action(
+ self, product_mrp_area_id, mrp_action_date, name, qty, action
+ ):
+ """Explode requirements."""
+ mrp_date_demand = mrp_action_date
+ if mrp_date_demand < date.today():
+ mrp_date_demand = date.today()
+ if not product_mrp_area_id.product_id.bom_ids:
+ return False
+ bomcount = 0
+ for bom in product_mrp_area_id.product_id.bom_ids:
+ if not bom.active or not bom.bom_line_ids:
+ continue
+ bomcount += 1
+ if bomcount != 1:
+ continue
+ for bomline in bom.bom_line_ids:
+ if bomline.product_qty <= 0.00 or \
+ bomline.product_id.type != 'product':
+ continue
+ if self._exclude_from_mrp(
+ 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=(product_mrp_area_id.mrp_transit_delay +
+ product_mrp_area_id.mrp_inspection_delay))
+ move_data = \
+ self._prepare_mrp_move_data_bom_explosion(
+ product_mrp_area_id, bomline, qty,
+ mrp_date_demand_2,
+ bom, name)
+ mrpmove_id2 = self.env['mrp.move'].create(move_data)
+ if hasattr(action, "mrp_move_down_ids"):
+ action.mrp_move_down_ids = [(4, mrpmove_id2.id)]
+ return True
+
+ @api.model
+ def create_action(
+ self, product_mrp_area_id, mrp_date, mrp_qty, name, values=None,
+ ):
+ if not values:
+ values = {}
+ if not isinstance(mrp_date, date):
+ mrp_date = fields.Date.from_string(mrp_date)
+ action_date, date_supply = \
+ self._get_action_and_supply_dates(product_mrp_area_id, mrp_date)
+ return self.create_planned_order(
+ product_mrp_area_id, mrp_qty, name, date_supply,
+ action_date, values=values)
+
+ @api.model
+ def create_planned_order(
+ self, product_mrp_area_id, mrp_qty,
+ name, mrp_date_supply, mrp_action_date, values=None,
+ ):
+ self = self.with_context(auditlog_disabled=True)
+
+ qty_ordered = values.get("qty_ordered", 0.0) if values else 0.0
qty_to_order = mrp_qty
while qty_ordered < mrp_qty:
qty = product_mrp_area_id._adjust_qty_to_order(qty_to_order)
qty_to_order -= qty
- move_data = self._prepare_mrp_move_data_supply(
+ order_data = self._prepare_planned_order_data(
product_mrp_area_id, qty, mrp_date_supply, mrp_action_date,
- mrp_action, name)
- mrpmove_id = self.env['mrp.move'].create(move_data)
+ name)
+ planned_order = self.env['mrp.planned.order'].create(order_data)
qty_ordered = qty_ordered + qty
- if mrp_action == 'mo':
- mrp_date_demand = mrp_action_date
- if mrp_date_demand < date.today():
- mrp_date_demand = date.today()
- if not product_mrp_area_id.product_id.bom_ids:
- continue
- bomcount = 0
- for bom in product_mrp_area_id.product_id.bom_ids:
- if not bom.active or not bom.bom_line_ids:
- continue
- bomcount += 1
- if bomcount != 1:
- continue
- for bomline in bom.bom_line_ids:
- if bomline.product_qty <= 0.00 or \
- bomline.product_id.type != 'product':
- continue
- if self._exclude_from_mrp(
- 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=(product_mrp_area_id.mrp_transit_delay +
- product_mrp_area_id.mrp_inspection_delay))
- move_data = \
- self._prepare_mrp_move_data_bom_explosion(
- product_mrp_area_id, bomline, qty,
- mrp_date_demand_2,
- bom, name)
- mrpmove_id2 = self.env['mrp.move'].create(move_data)
- mrpmove_id.mrp_move_down_ids = [(4, mrpmove_id2.id)]
+ if product_mrp_area_id.supply_method == 'manufacture':
+ self.explode_action(
+ product_mrp_area_id, mrp_action_date,
+ name, qty, planned_order)
+
values['qty_ordered'] = qty_ordered
log_msg = '[%s] %s: qty_ordered = %s' % (
product_mrp_area_id.mrp_area_id.name,
@@ -292,6 +295,8 @@ class MultiLevelMrp(models.TransientModel):
domain += [('mrp_area_id', 'in', mrp_areas.ids)]
self.env['mrp.move'].search(domain).unlink()
self.env['mrp.inventory'].search(domain).unlink()
+ domain += [('fixed', '=', False)]
+ self.env['mrp.planned.order'].search(domain).unlink()
logger.info('End MRP Cleanup')
return True
@@ -356,8 +361,7 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def _init_mrp_move_from_forecast(self, product_mrp_area):
- locations = self.env['stock.location'].search(
- [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
+ locations = product_mrp_area.mrp_area_id._get_locations()
today = fields.Date.today()
estimates = self.env['stock.demand.estimate'].search([
('product_id', '=', product_mrp_area.product_id.id),
@@ -383,8 +387,7 @@ class MultiLevelMrp(models.TransientModel):
# show moves with an action
@api.model
def _in_stock_moves_domain(self, product_mrp_area):
- locations = self.env['stock.location'].search(
- [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
+ locations = product_mrp_area.mrp_area_id._get_locations()
return [
('product_id', '=', product_mrp_area.product_id.id),
('state', 'not in', ['done', 'cancel']),
@@ -395,8 +398,7 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def _out_stock_moves_domain(self, product_mrp_area):
- locations = self.env['stock.location'].search(
- [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
+ locations = product_mrp_area.mrp_area_id._get_locations()
return [
('product_id', '=', product_mrp_area.product_id.id),
('state', 'not in', ['done', 'cancel']),
@@ -443,21 +445,17 @@ class MultiLevelMrp(models.TransientModel):
'current_qty': poline.product_qty,
'mrp_date': mrp_date,
'current_date': poline.date_planned,
- 'mrp_action': 'none',
'mrp_type': 's',
- 'mrp_processed': False,
'mrp_origin': 'po',
'mrp_order_number': poline.order_id.name,
'parent_product_id': None,
- 'running_availability': 0.00,
'name': poline.order_id.name,
'state': poline.order_id.state,
}
@api.model
def _init_mrp_move_from_purchase_order(self, product_mrp_area):
- location_ids = self.env['stock.location'].search(
- [('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
+ location_ids = product_mrp_area.mrp_area_id._get_locations()
picking_types = self.env['stock.picking.type'].search(
[('default_location_dest_id', 'in',
location_ids.ids)])
@@ -528,8 +526,9 @@ class MultiLevelMrp(models.TransientModel):
last_qty = 0.00
onhand = product_mrp_area.qty_available
grouping_delta = product_mrp_area.mrp_nbr_days
- for move in product_mrp_area.mrp_move_ids.filtered(
- lambda m: m.mrp_action == 'none'):
+ for move in product_mrp_area.mrp_move_ids:
+ if self._exclude_move(move):
+ continue
if last_date and (
fields.Date.from_string(move.mrp_date)
>= last_date + timedelta(days=grouping_delta)) and (
@@ -539,7 +538,7 @@ class MultiLevelMrp(models.TransientModel):
< product_mrp_area.mrp_minimum_stock):
name = 'Grouped Demand for %d Days' % grouping_delta
qtytoorder = product_mrp_area.mrp_minimum_stock - last_qty
- cm = self.create_move(
+ cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=last_date,
mrp_qty=qtytoorder,
@@ -553,7 +552,7 @@ class MultiLevelMrp(models.TransientModel):
product_mrp_area.mrp_minimum_stock or \
(onhand + last_qty) < \
product_mrp_area.mrp_minimum_stock:
- if not last_date:
+ if not last_date or last_qty == 0.0:
last_date = fields.Date.from_string(move.mrp_date)
last_qty = move.mrp_qty
else:
@@ -565,7 +564,7 @@ class MultiLevelMrp(models.TransientModel):
if last_date and last_qty != 0.00:
name = 'Grouped Demand for %d Days' % grouping_delta
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty
- cm = self.create_move(
+ cm = self.create_action(
product_mrp_area_id=product_mrp_area, mrp_date=last_date,
mrp_qty=qtytoorder, name=name)
qty_ordered = cm.get('qty_ordered', 0.0)
@@ -573,6 +572,11 @@ class MultiLevelMrp(models.TransientModel):
nbr_create += 1
return nbr_create
+ @api.model
+ def _exclude_move(self, move):
+ """Improve extensibility being able to exclude special moves."""
+ return False
+
@api.model
def _mrp_calculation(self, mrp_lowest_llc, mrp_areas):
logger.info('Start MRP calculation')
@@ -591,25 +595,22 @@ class MultiLevelMrp(models.TransientModel):
for product_mrp_area in product_mrp_areas:
nbr_create = 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 product_mrp_area.mrp_move_ids:
- if move.mrp_action == 'none':
- if (onhand + move.mrp_qty) < \
- product_mrp_area.mrp_minimum_stock:
- qtytoorder = \
- product_mrp_area.mrp_minimum_stock - \
- onhand - move.mrp_qty
- cm = self.create_move(
- product_mrp_area_id=product_mrp_area,
- mrp_date=move.mrp_date,
- mrp_qty=qtytoorder, name=move.name)
- qty_ordered = cm['qty_ordered']
- onhand += move.mrp_qty + qty_ordered
- nbr_create += 1
- else:
- onhand += move.mrp_qty
+ if self._exclude_move(move):
+ continue
+ qtytoorder = product_mrp_area.mrp_minimum_stock - \
+ onhand - move.mrp_qty
+ if qtytoorder > 0.0:
+ cm = self.create_action(
+ product_mrp_area_id=product_mrp_area,
+ mrp_date=move.mrp_date,
+ mrp_qty=qtytoorder, name=move.name)
+ qty_ordered = cm['qty_ordered']
+ onhand += move.mrp_qty + qty_ordered
+ nbr_create += 1
+ else:
+ onhand += move.mrp_qty
else:
nbr_create = self._init_mrp_move_grouped_demand(
nbr_create, product_mrp_area)
@@ -618,7 +619,7 @@ class MultiLevelMrp(models.TransientModel):
nbr_create == 0:
qtytoorder = \
product_mrp_area.mrp_minimum_stock - onhand
- cm = self.create_move(
+ cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=date.today(),
mrp_qty=qtytoorder,
@@ -654,35 +655,30 @@ class MultiLevelMrp(models.TransientModel):
FROM mrp_move
WHERE product_mrp_area_id = %(mrp_product)s
AND mrp_type = 's'
- AND mrp_action = 'none'
GROUP BY mrp_date
"""
+ params = {
+ 'mrp_product': product_mrp_area.id,
+ }
+ return query, params
+
+ @api.model
+ def _get_planned_order_groups(self, product_mrp_area):
+ query = """
+ SELECT due_date, sum(mrp_qty)
+ FROM mrp_planned_order
+ WHERE product_mrp_area_id = %(mrp_product)s
+ GROUP BY due_date
+ """
params = {
'mrp_product': product_mrp_area.id
}
return query, params
- @api.model
- 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 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': product_mrp_area.id,
- 'excluded_mrp_actions': tuple(exclude_mrp_actions,)
- }
- return query, params
-
@api.model
def _init_mrp_inventory(self, product_mrp_area):
mrp_move_obj = self.env['mrp.move']
+ planned_order_obj = self.env['mrp.planned.order']
# Read Demand
demand_qty_by_date = {}
query, params = self._get_demand_groups(product_mrp_area)
@@ -695,44 +691,49 @@ class MultiLevelMrp(models.TransientModel):
self.env.cr.execute(query, params)
for mrp_date, qty in self.env.cr.fetchall():
supply_qty_by_date[mrp_date] = qty
- # Read supply actions
- # 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(product_mrp_area)
+ # Read planned orders:
+ planned_qty_by_date = {}
+ query, params = self._get_planned_order_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
+ planned_qty_by_date[mrp_date] = qty
# Dates
- mrp_dates = set(mrp_move_obj.search([
+ moves_dates = mrp_move_obj.search([
('product_mrp_area_id', '=', product_mrp_area.id)],
- order='mrp_date').mapped('mrp_date'))
+ order='mrp_date').mapped('mrp_date')
+ action_dates = planned_order_obj.search([
+ ('product_mrp_area_id', '=', product_mrp_area.id)],
+ order='due_date').mapped('due_date')
+ mrp_dates = set(moves_dates + action_dates)
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?
+ running_availability = on_hand_qty
for mdt in sorted(mrp_dates):
mrp_inventory_data = {
'product_mrp_area_id': product_mrp_area.id,
'date': mdt,
}
- demand_qty = 0.0
- supply_qty = 0.0
- if mdt in demand_qty_by_date.keys():
- demand_qty = demand_qty_by_date[mdt]
- mrp_inventory_data['demand_qty'] = abs(demand_qty)
- if mdt in supply_qty_by_date.keys():
- supply_qty = supply_qty_by_date[mdt]
- mrp_inventory_data['supply_qty'] = abs(supply_qty)
- if mdt in supply_actions_qty_by_date.keys():
- mrp_inventory_data['to_procure'] = \
- supply_actions_qty_by_date[mdt]
+ demand_qty = demand_qty_by_date.get(mdt, 0.0)
+ mrp_inventory_data['demand_qty'] = abs(demand_qty)
+ supply_qty = supply_qty_by_date.get(mdt, 0.0)
+ mrp_inventory_data['supply_qty'] = abs(supply_qty)
mrp_inventory_data['initial_on_hand_qty'] = on_hand_qty
on_hand_qty += (supply_qty + demand_qty)
mrp_inventory_data['final_on_hand_qty'] = on_hand_qty
+ # Consider that MRP plan is followed exactly:
+ running_availability += supply_qty \
+ + demand_qty + planned_qty_by_date.get(mdt, 0.0)
+ mrp_inventory_data['running_availability'] = running_availability
- self.env['mrp.inventory'].create(mrp_inventory_data)
+ inv_id = self.env['mrp.inventory'].create(mrp_inventory_data)
+ # attach planned orders to inventory
+ planned_order_obj.search([
+ ('due_date', '=', mdt),
+ ('product_mrp_area_id', '=', product_mrp_area.id),
+ ]).write(
+ {'mrp_inventory_id': inv_id.id})
@api.model
def _mrp_final_process(self, mrp_areas):
@@ -745,16 +746,6 @@ class MultiLevelMrp(models.TransientModel):
for product_mrp_area in product_mrp_area_ids:
# Build the time-phased inventory
self._init_mrp_inventory(product_mrp_area)
-
- # Complete info on mrp_move (running availability and nbr actions)
- qoh = product_mrp_area.qty_available
-
- moves = self.env['mrp.move'].search([
- ('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
logger.info('End MRP final process')
@api.multi